《Effective C++》学习笔记——条款44

来源:互联网 发布:c语言长整型数 编辑:程序博客网 时间:2024/06/05 08:20




七、模板与泛型编程


条款44:将与参数无关的代码抽离templates




template

Template是节省时间和避免代码重复的一个奇妙的方法。
你只需要键入一个class template,剩下的就可以留给编译器去具现化那些相关类和函数。

  • class templates的成员函数只有在被使用时才被暗中具现化,所以只有被具现化的所有函数都使用一遍,你才会获得所有函数。

Function templates也差不多,只需要写一个function template,然后就可以让编译器去干剩下的活了。


但是,使用templates可能会导致代码膨胀:其二进制码带着重复的代码、数据或两者都有。
所以,需要知道如何避免这样的错误 —— 共性与变性分析

  • 对于函数:
    分析两个函数,找出共同的部分和变化的部分,把共同的部分搬到一个新函数去,保留了两个函数,找出共同的部分和变化的部分,把共同部分搬到一个新函数去,保留变化的部分在原函数中不动。
  • 对于类:
    同函数,令原先的classes取用共同特性,而原classes的互异部分仍然在原位置不动。
  • 对于template:
    在non-template代码中,重复十分明确:你可以看到两个函数或两个classes之间有所重复;在template代码中,重复隐晦的,毕竟只存在一份template源码,所以必须训练自己去感受当template被具现化多次时可能发生的重复。


一个例子

想为固定尺寸的正方矩阵编写一个template。该矩阵的性质之一是支持矩阵的逆运算。

// template支持 n x n 矩阵,元素是类型为T的objectstemplate<typename T, std::size_t n>     class SquareMatrix  {public:    ...    void invert();    // 求逆矩阵};

这个template接受一个类型参数T,除此之外还接受一个类型为size_t的参数(这是一个非类型参数)。
然后,来看一下应用:

SquareMatrix<double, 5> sm1;...sm1.invert();SquareMatrix<double, 10> sm2;...sm2.invert();

这段代码将具现化两份invert,但这两份invert除了常量5和10,其他部分完全相同。
这种template代码膨胀非常典型。

对template代码膨胀的修改

  1. 第一次优化修改
    为它们建立一个带数值参数的函数,然后以5和10来调用这个带参数的函数,从而不重复代码。
template<typename T>class SquareMatrixBase  {protected:    ...    void invert(std::size_t matrixSize);    ...};template<typename T, std::size_t n>class SquareMatrix: private SquareMatrixBase<T>  {private:    using SquareMatrixBase<T>::invert;    // 避免遮掩base版的invertpublic:    ...    void invert()  {  this->invert(n);  }    // 制造一个inline调用,调用base class版的invert};
  • SquareMatrixBase::invert只是企图成为”避免derived classes代码重复”的一种方法,所以它以protected替换public
  • 这些函数使用this->记号,因为若不这样做,便如条款43所说,模板化基类内的函数名称会被derived classes掩盖。
  • SquareMatrix和SquareMatrixBase之间的继承关系是private。这反应一个事实:这里的base class只是为了帮助derived classes实现,不是为了表现SquareMatrix和SquareMatrixBase之间的is-a关系。
  • 缺陷:
    SquareMatrixBase::invert如何知道该操作什么数据?虽然它从参数中知道矩阵尺寸,但它如何知道哪个特定矩阵的数据在哪里呢。
  • 参考:
    一个可能的做法是为SquareMatrixBase::invert添加另一个参数,也许是个指针,指向一块用来放置矩阵数据的内存起始点。那行得通,但十之八九invert不是唯一一个可写为”形式与尺寸无关并可移至SquareMatrixBase内”的”SquareMatrix函数。如果有若干这样的函数,我们唯一要做的就是找出保存矩阵元素值的那块内存。我们可以对所有这样的函数添加一个额外参数,却得一次又一次地告诉SquareMatrixBase相同的信息,这样做不是很好。



2. 第二次修改
令SquareMatrixBase贮存一个指针,指向矩阵数值所在的内存。而只要它存储了那些东西,也就可能存储矩阵的尺寸。

template<typename T>class SquareMatrixBase  {protected:    SquareMatrixBase(std::size_t n, T* pMenu)    :size(n), pData(pMem)  {  }    void setDataPtr(T* ptr)  {  pData = ptr;  }    ...private:    std::size_t size;    T* pData;};

这允许derived class决定内存的分配方式,某些实现版本也许会决定将矩阵数据存储在SquareMatrix对象内部:

template<typename T, std::size_t n>class SquareMatrix: private SquareMatrixBase<T>  {public:    SquareMatrix(): SquareMatrixBase<T>(n, data)    // 送出矩阵大小和数据指针给base class    {  }    ...private:    T data[n*n];};

这种类型的对象就不需要动态分配内存,但对象自身可能非常大。另一种做法就是把每一个矩阵的数据放进heap(也就是通过new来分配内存)

template<typename T, std::size_t n>class SquareMatrix: private SquareMatrixBase<T>  {public:    SquareMatrix(): SquareMatrixBase<T>(n, 0), pData(new T[n*n])    // 将base class的数据指针设为null,为矩阵内容分配内存    {  this->setDataPtr(pData.get());  }    // 将指向该内存的指针存储起来,然后将它的一个副本交给base class    ...private:    boost::scoped_array<T> pData;};

硬是绑着矩阵尺寸的那个invert版本,有可能生成比共享版本(其中尺寸乃以函数参数传递或存储在对象内)更佳的代码。
从另一个角度看,不同大小的矩阵只拥有单一版本的invert,可减少执行文件大小,也就因此降低程序的working set(指对一个在”虚内存环境”下执行的进程而言,其所使用的那一组内存页)大小,并强化指令高速缓存区内的引用集中化。

本条款只讨论由non-type template parameters(非类型模板参数)带来的膨胀,其实type parameters(类型参数)也会导致膨胀。


请记住

  • Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
  • 因非类型模板参数(non-type template parameters)而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。
  • 因类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representations)的具现类型(instantiation types)共享实现码。
0 0