标签:重复 类模版 ace 存在 info strong 参数传递 依赖 等等
使用模板是节省时间和避免代码重用的很好的方法。你不需要手动输入20个相同的类名,每个类有15个成员函数,相反,你只需要输入一个类模板,然后让编译器来为你实例化20个特定的类和300个你需要的函数。(只有在被使用的情况下类模版的成员函数才会被隐式的实例化,所以只有在300个函数被实际用到的情况下才会生成300个成员函数。)函数模板同样吸引人。你不用手动实现许多函数,你只需要实现一个函数模板,然后让编译器来做余下的事情。
然而在有些时候,如果你不小心,使用模板会导致代码膨胀(code bloat):产生重复代码或者数据的二进制文件,或者两者都有。结果可能是源码看起来合身整齐,但是目标代码(object code)臃肿松弛。臃肿松弛很不好,因此你需要知道如果避免这样的二进制浮夸。
你的主要工具有着很威风的名字:共性和可变性分析(commonality and variability analysis),但是这个概念很平常。即使在你的编程生涯中从未实现过一个模板,你也总是会做这样的分析。
当你正在实现一个函数,你意识到函数实现的某些部分同另外一个函数实现基本上是相同的 ,你会重复这些代码么?当然不会。你将两个函数的公共代码提取出来,放进第三个函数中,然后在两个函数中调用这个新函数。总结一下就是,你对两个函数进行分析,找到相同和不同的部分,将相同的部分移到一个新的函数中去,将不同的部分保留在原来的函数中。类似的,如果你正在实现一个类,你意识到类中的一部分另一个类中的一部分是相同的,你不应该重写相同的部分。相反,你可以将相同的部分移到一个新类中,然后使用继承或者组合(Item 32,Item 38,Item 39)让原始类访问共同的特性。原始类中不同的部分仍然保留在原来的位置。
当实现模板的时候,你也会做相同的分析,你会使用相同的方式来阻止重复,但是这里有一个让你伤痛的地方。在非模板(non-template)代码中,重复是显示的:你可以看到在函数之间或者类之间会有代码重复。在模板代码中,重复是隐式的:只有一份模板源码,所以你必须训练你自己当一个模板被实例化多次的时候,你能够感觉到重复会不会发生。
例如,假设你想为固定大小的矩阵实现一个模板,需要支持矩阵的转置。
1 template<typename T, // template for n x n matrices of 2 std::size_t n> // objects of type T; see below for info 3 class SquareMatrix { // on the size_t parameter 4 public: 5 ... 6 7 void invert(); // invert the matrix in place 8 9 };
这个模板带了一个类型参数,T,但是也带了一个类型size_t的参数,一个非类型(non-type)参数。非类型参数比类型参数少了共性,但是它们是完全合法的,并且在这个例子中,它们也能非常自然。
现在考虑下面的代码:
1 SquareMatrix<double, 5> sm1; 2 3 ... 4 5 sm1.invert(); 6 7 // call SquareMatrix<double, 5>::invert 8 9 SquareMatrix<double, 10> sm2; 10 11 12 13 ... 14 15 16 17 sm2.invert(); 18 19 // call SquareMatrix<double, 10>::invert 20 21
在这里将会实例化invert的两份拷贝。这两个函数并不相同,因为一个在5*5的矩阵上工作,另外一个在10*10的矩阵上工作,但是如果不考虑常量5和10,这两个函数将会是一样的。这是使得包含模板的代码出现膨胀的典型方式。
如果你看到两个函数,它们的所有字符都是相同的,除了一个版本使用5而另外一个版本使用10,你接下来会做什么?你的直觉是会创建一个带一个参数的函数版本,然后以5或者10为入参调用这个函数而不是重复代码。你的直觉能够很好的为你服务!这是实现SquareMatrix的第一关:
1 template<typename T> // size-independent base class for 2 class SquareMatrixBase { // square matrices 3 protected: 4 ... 5 void invert(std::size_t matrixSize); // invert matrix of the given size 6 ... 7 }; 8 template<typename T, std::size_t n> 9 class SquareMatrix: private SquareMatrixBase<T> { 10 private: 11 using SquareMatrixBase<T>::invert; // make base class version of invert 12 // visible in this class; see Items 33 13 // and Item 43 14 public: 15 ... 16 void invert() { invert(n); } // make inline call to base class 17 }; // version of invert
正如你所看到的,带参数的invert版本被放在基类SquareMatrixBase中。像SquareMatrix一样,SquareMatrixBase是一个模板,但是与SquareMatrix不同的是,它在矩阵中只对对象类型进行模板化。因此,包含一个给定类型对象的所有矩阵将会分享一个单一的SquareMatrixBase类。这样它们会分享SquareMatrixBase类的invert版本的单一拷贝。(你不能将其声明为inline,因为一旦被inline了,每个SquareMatrix::invert的实例都会得到SquareMatrixBase::invert代码的一份拷贝(看Item 30),你会发现你有回到了对象代码重复的原点。)
SquareMatrixBase::invert只被用来在派生类中防止代码重复,所以是protected而不是public的。调用它的额外开销应该是0,因为派生类的inverts调用基类版本使用了inline函数。(inline是隐式的 见Item 30)同时注意SquareMatrix和SquareMarixBase之间的继承是private的。这精确的反映出一个事实:使用基类的唯一原因是帮助派生类的实现,并非表达出SquareMatrix和SquareMatrixBase之间的“is-a”关系。(有关private继承的信息,见Item 39)
到现在为止看上去都很好,但是还有一个我们没有处理的棘手的问题。SquareMatrixBase::invert如何知道在什么数据上进行操作?它从参数中得知矩形的大小,但是它如何知道为特殊矩阵提供的数据在哪里?大概只有派生类才会知道。派生类如何同基类进行通讯才能让基类执行invert?
一个可能的方法是向SquareMatrixBase::invert中添加另外一个参数,可能是一个指向一块内存的指针,内存中存放矩形数据。这种方法可以工作,但是十有八九,invert不是存在于SquareMatrix中的能够以独立于size的方式重写的,并且移入SquareMatrixBase中的唯一函数。如果有几个这样的函数,我们就需要一种方法能够找到存放矩形数据的内存,我们可以为所有的函数添加一个额外的参数,但是如此以来我们就重复告诉了SquareMatrixBase同样的信息。这看上去是错误的。
一个替换方法是让SquareMatrixBase存储一个指向存放矩形数据的内存的指针。这同存放矩形大小有相同的效果。结果如下:
1 template<typename T> 2 class SquareMatrixBase { 3 protected: 4 SquareMatrixBase(std::size_t n, T *pMem) // store matrix size and a 5 : size(n), pData(pMem) {} // ptr to matrix values 6 7 void setDataPtr(T *ptr) { pData = ptr; } // reassign pData 8 9 ... 10 11 private: 12 13 14 15 std::size_t size; // size of matrix 16 17 T *pData; // pointer to matrix values 18 19 20 };
这就让派生类来决定如何分配内存。一些实现会在SquareMatrix对象内部存储矩形数据:
1 template<typename T, std::size_t n> 2 class SquareMatrix: private SquareMatrixBase<T> { 3 public: 4 SquareMatrix() // send matrix size and 5 : SquareMatrixBase<T>(n, data) {} // data ptr to base class 6 ... 7 private: 8 T data[n*n]; 9 };
这种类型的对象没有必要做动态内存分配,但是对象本身可能会非常大。一个替换的方法是为每个矩形在堆上存放数据:
1 template<typename T, std::size_t n> 2 class SquareMatrix: private SquareMatrixBase<T> { 3 public: 4 SquareMatrix() // set base class data ptr to null, 5 : SquareMatrixBase<T>(n, 0), // allocate memory for matrix 6 pData(new T[n*n]) // values, save a ptr to the 7 { this->setDataPtr(pData.get()); } // memory, and give a copy of it 8 9 ... // to the base class 10 11 private: 12 boost::scoped_array<T> pData; // see Item 13 for info on 13 14 15 16 }; // boost::scoped_array
不管将数据存放在哪里,从代码膨胀的角度来说,关键结果是现在很多(可能是所有的)SquareMatrix的成员函数可以简单的inline调用基类的(non-inline)函数版本,所有持有相同类型数据的矩形共享基类中的函数,不管size是多少。同时,不同size的SquareMatrix对象属于不同类型,所以即使SquareMatrix<double,5>和SquareMatrix<double,10>对象在SquareMatrixBase<double>中使用相同的成员函数,把一个SquareMatrix<double,5>对象传给一个需要SquareMatrix<double,10>的函数是没有机会的。好还是不好呢。
好是好,但是需要付出代价。矩形size大小固定的invert版本比按函数参数传递size大小(或者存储在对象中)的invert版本可能产生更好的代码。例如,在指定size的版本中,sizes是编译期常量,因此是常量传播优化的合格者,也可以把其放入生成指令中作为直接操作数。这在同size无关的版本中无法做到。
从另外一个方面,为不同size的矩阵只提供一个invert版本可以减小可执行程序的大小,这能减少程序的工作集大小,并且能够强化指令高速缓存的引用集中化。这些东西能够使得程序运行速度更快,并且相对size指定的版本才能做出的优化,它可能会做出更好的补偿。哪种方法效果更好?唯一的方法是两种方法都试一下,在你的特定平台和有代表性的数据集上观察它们的行为。
另外一个有关效率的需要考虑的地方是有关对象的大小。如果你不介意,将size大小无关的版本向上移动到基类中会增加每个对象的大小。例如,在我刚刚展示的代码中,每个SquareMatrix对象有一个指向SquareMatrixBase类中数据的指针。即使每个派生类中已经有取得数据的方法,这也为每个SquareMatrix对象至少增加一个指针的大小。我们可以修改设计来去掉指针,但是这也是需要付出代价的。例如,让基类存储一个指向数据的protected指针,但会导致封装性的降低(Item 22).它同样能导致资源管理并发症:如果基类存储了指向矩阵数据的指针,但是数据既有可能是动态分配的也可能存储在派生类对象中(正如我们看到的),如何决定是不是需要delete指针?这样的问题是有答案的,但是你做的越精细事情就变得越复杂。从某种意义上讲,有一点代码重复开始开起来有点幸运了。
这个条款仅仅讨论了由于非类型模板参数导致的代码膨胀,但是类型参数同样可以导致代码膨胀。例如,在许多平台中,int和long有着相同的二进制表示,所以在成员函数中使用vector<int>和vector<long>看起来会一样,这正是代码膨胀的定义。一些连接器会把相同的代码实现整合到一起,但是有一些不会,这就意味着由模板实例化的int和long版本会在一些环境中导致代码膨胀。类似的,在大多数平台上,所有的指针类型有着相同的二进制表示,所以带指针类型的模板(例如,list<int*>,list<const*>,list<SquareMatrix<long,3>*>等等)应该通常能够为每个成员函数使用一个单一的底层实现。特别的,这就意味着实现一个强类型指针(T* 指针)的成员函数时,让它们调用一个无类型指针的函数(void*指针)。一些标准C++库的实现为模板就是这么做的(如vector,deque,和list)。如果你关心在你的模板中出现的代码膨胀问题,你可能就会想开发出做相同事情的模板。
读书笔记 effective c++ Item 44 将与模板参数无关的代码抽离出来
标签:重复 类模版 ace 存在 info strong 参数传递 依赖 等等
原文地址:http://www.cnblogs.com/harlanc/p/6680216.html