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

来源:互联网 发布:音乐相册制作软件 编辑:程序博客网 时间:2024/05/17 08:52

条款44:将与参数无关的代码抽离templates
(Factor parameter-independent code out of templates.)

内容:
     自从我们进入模板编程的第一天,我们就意识到template不仅大大地减少了代码的执行时间,而且能够避免代码潜在的重复.我们不在需要为20个相似的classes而每一个都带有15个成员函数编写独立的一份代码去分别实现,我们只需要键入一个class template,留给编译器去具现化那20个你需要的相关classes和300个函数.如此神奇的技术是不是令你感到很兴奋.是的,这个对每一个软件开发人员来说无疑是一个重大的编码优化手段,然而有时候,如果你不小心,使用templates可能会导致代码膨胀:其二进制码带着重复(或几乎重复)的代码、数据,或两者.
     在你编码的时候,通常你有一个强大的工具.那就是:共性与变性分析.其概念从字面上我们就可以了解,即使你从未写过一个template,你始终在进行着这样的分析.当你在写两个函数的时候,你发现它们之间有着相同的部分,此时的你惯性般地将他们之间"公共部分"提出来,作为第三个函数,然后让前两个函数都调用这个第三个函数;同样的道理,当你编写某个class时候,你发现其某些部分与另外一个class的某些部分相同,你也不会去重复该相同部分,而是把相同部分搬移到新的class去,然后在使用复合或继承,令原classes取用这些共同特性.而原classes的特异部分则保持不动. 好,我们将这种分析方法推而广之,它对template同样适用.但在template代码中,重复是隐晦的:毕竟只存在一份template源码,所以你编码时必须万分小心去防止template具现化多次时可能造成的重复.
     举个例子,假设现在你要为固定尺寸的矩阵编写一个template类,该类声明要支持矩阵的逆运算.你开始了你的代码:
     template <typename T, std::size_t n> //矩阵元素类型T,尺寸大小为n
     class SquareMatrix{
     public:
         ...
         void invert(); //逆运算
     };
     写出这样代码很自然,接下来我们考虑如何使用这个template:
     SquareMatrix<double,5> square1;
     ...
     square1.invert(); //调用 SquareMatrix<double,5>::invert
     SquareMatrix<double,10> square2;
     ...
     square2.invert(); //调用 SquareMatrix<double,10>::invert
     这会发生什么事情?这些函数并非完完全全相同.但除了常量5和10,这两个函数的其它部分完全相同.这就是template引出代码膨胀的一个典型例子.
     如果你的代码中遇到这样的情况,你会怎么去"亡羊补牢".你的本能会让你为它们建立一个带数值参数的函数,然后用5和10来调用这个带参函数,这样就不会导致代码重复了.好,想法不错,我们来实现你的idea.
     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版本的invert
     public:
         ...
         void invert(){ this->invert(n); }
     };
     这里请注意SquareMatrix和SquareMatrixBase之间的继承关系是private.这反应一个事实:这里的base class只是为了帮助derived class实现,而不是为了表现SquareMatrix与SquareMatrixBase之间是一个is-a关系(关于private继承,见条款39).
     到目前为止,情况还令人满意.不过,现在有一个值得我们去思考的问题出现了:SquareMatrixBase::invert如何知道该操作什么数据?虽然它知道了矩阵的尺寸,但怎么能够知道那个矩阵的数据放在什么地方了倪?这里的一个可行的方法是令SquareMatrixBase储存一个指针,该指针指向矩阵数值所在的内存.那我们要操作的东西就有了.哈哈,that's perfect!成果貌似是这样滴:
     template <typename T>
     class SquareMatrixBase{
     protected:
         SquareMatrixBase(std::size_t n, T* memory)
         :size_(n),data_(memory){}  
         void setData(T* data){ data_ = data; }
         ...
     private:
         std::size_t size_; //矩阵大小
         T* data_;          //矩阵数据
     };
     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中:
     template <typename T,std::size_t n>
     class SquareMatrix:private SquareMatrixBase<T>{
     public:
         SquareMatrix()
         :SquareMatrixBase<T>(n,0),data_(new T[n*n]){ 
             this->setData(data_.get()); //使得base class获得矩阵数据
         }
         ...
     private:
         boost::scoped_array[T] data_;
     };
     喔喔,这样的解决方案是不是很棒?是的,很棒,但必须付出代价.带有尺寸模板参数的那个invert版本,可能生成比共享版本(就是以函数参数传递尺寸或存储在对象内)更佳的代码.比如在尺寸专属版本中,尺寸是个编译期常量,因此可以籍由常量的广传达到最优化,包括把它们折进被生成指令中称为直接操作数.这在"与尺寸无关"的版本中是无法办到的.而另一个方面,不同大小的矩阵只拥有单一版本的invert,可减少执行文件大小,也就因此降低程序进程所使用的那一组内存页大小,并强化指令高速缓存区内的引用集中化.这些都可能使得程序执行的更加快速,超越"尺寸专属版"invert的最优化效果. 那哪一个影响占主要地位?要想知道这个答案,唯有让两者尝试并观察你的平台行为以及面对代表性数据组时的行为.
     另一个效能评比所关心的主题就是对象大小.这里我不再详细表述了,大家自己可以分析一下.
     本条款只讨论由non-type template parameter(非类型模板参数)带来的膨胀,其实type parameter(类型参数)会导致膨胀.具体解释请看原书作者的表述,限于篇幅的问题,在这里我就不累赘表述了.
     请记住:
     ■ Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系.
     ■ 因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数. 
     ■ 因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码.

原创粉丝点击