在非模板代码中,重复十分明确:你可以“看”到两个函数或两个class之间有所重复。然而在模板代码中,重复是隐晦的:毕竟只存在一份模板源码,所以你必须培养自己去感受当模板被具现化多次时可能发生的重复。

例如:下面的模板

template<typename T,size_t n>        //template支持n * n矩阵,元素类型为T的对象

class SquareMatrix

{

public:

...

void invert();                  //求逆矩阵

};

这个模板接受一个类型参数T,除此之外还接受一个类型为size_t的参数,那是个非类型参数。这种参数和类型参数比起来较不常见,但是完全合法。

看如下代码:

SquareMatrix<double,5> sm1;

...

sm1.invert();                       //调用SquareMatrix<double,5>::invert

SquareMatrix<double,10> sm2;

...

sm2.invert();                       //调用SquareMatrix<double,10>::invert

这会具现化两份invert。这些函数并非完完全全相同,因为其中一个操作的是5*5矩阵而另一个操作的是10*10矩阵,但除了常量5和10,两个函数的其他部分完全相同。这是模板引出代码膨胀的一个典型例子。怎么办呢?本能是建立一个带数值参数的函数,然后以5和10来调用这个带参数的函数,而不重复代码。所以下面是对SquareMatrix的第一次修改:

template<typename T>                   //与尺寸无关的基类

class SquareMatrixBase

{

protected:

...

void invert(size_t maxtrixSize);    //以给定尺寸求逆矩阵

...

};

template<typename T,size_t n>

class SquareMatrix : private SquareMatrixBase<T>

{

private:

using SquareMatrixBase<T>::invert;    //避免遮掩base版的invert

public:

...

void invert(){this->invert(n);}      //一个inline调用,调用基类版的invert。

};

带参数的invert位于基类SquareMatrixBase中,和SquareMatrix一样,SquareMatrixBase也是个模板,不同的是它只对“矩阵元素对象的类型”参数化,不对矩阵的尺寸参数化。因此对于某给定之元素对象类型,所有矩阵共享同一个(也是唯一一个)SquareMatrixBase类。它们也将因此共享这唯一一个类内的invert。

SquareMatrixBase::invert只是企图成为“避免派生类代码重复”的一种方法,所以它以protected替换public。调用它而造成的额外成本应该是0,因为派生类的invert调用基类版本时用的是inline调用。这些函数使用“this->”记号,因为若不这样做,模板化基类(如SquareMatrixBase<T>)内的函数名称会被派生类覆盖(见上一篇博文)。注意SquareMatrix和SquareMatrixBase之间的继承关系是private,这反映了一个事实:这里的基类只是为了帮助派生类实现,不是为了表现SquareMatrix和SquareMatrixBase之间的is-a关系。

目前为止一切都好,但还是有一些棘手的问题没有解决。SquareMatrixBase::invert如何知道该操作什么数据?虽然它从参数中知道矩阵尺寸,但它如何知道哪个特定矩阵的数据在哪儿?想必只有派生类知道。派生类如何联络其基类做逆运算动作?一个可能的做法是为SquareMatrixBase::invert添加另一个参数,也许是个指针,指向一块用来放置矩阵数据的内存起点。那可以,但十之八九invert不是唯一一个可写为“形式与尺寸无关并可移至SquareMatrixBase内”的SquareMatrix函数。如果有若干这样的函数,我们唯一需要做的就是找出保存矩阵元素的那块内存。我们可以对所有这样的函数添加一个额外参数,却得一次又一次地告诉SquareMatrixBase相同的信息,这样也不好。

另一办法是令SquareMatrixBase存储一个指针,指向矩阵数值所在的内存,而只要它存储了那些东西,也就可能存储了矩阵尺寸(注意:这个只用在派生类到基类的构造函数中传递一次矩阵数值而上面的方法需要在每个函数中传递一次。)。看起来像这样:

template<typename T>

class SquareMatrixBase

{

protected:

SquareMatrixBase(size_t n, T* pMem)     //存储矩阵大小和一个指针,指向矩阵数值

:size(n),pData(pMem){}

void setDataPtr(T* ptr){pData = ptr;}   //重新赋值给pData

...

private:

size_t size;                            //矩阵大小

T* pData;                               //指针,指向矩阵的内容

};

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

tempalte<typename T,size_t n>

class SquareMatrix : private SquareMatrixBase<T>

{

public:

SquareMatrix():SquareMatrixBase<T>(n,data);  //送出矩阵大小和数据指针给基类

...

private:

T data[n * n];

};

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

template<typename T,szie_t n>

class SquareMatrix : private SquareMatrixBase<T>

{

public:

SquareMatrix():SquareMatrixBase<T>(n,0)       //将基类的数据指针设为null

,pData(new T[n * n])                          //为矩阵内容分配内存,将指向该内存的指针存储起来

{this->setDataPtr(pData.get());}              //然后将它的一个副本交给基类

...

private:

boost::scoped_array<T> pData;

};

(scoped_array是一个non-TR1智能指针,是个auto_ptr-like智能指针,用来动态分配数组)。

无论数据存储于何处,从膨胀的角度检讨之,关键是现在许多——说不定是所有——SquareMatrix成员函数可以单纯地以inline方式调用基类版本,后者由“持有相同型元素”(无论矩阵大小)之所有矩阵共享。在此同时,不同大小的SquareMatrix对象有着不同的类型,所以即使(例如SquareMatrix<double,5>和SquareMatrix<double,10>)对象使用相同的SquareMatrix<double>成员函数,我们也没机会传递一个SquareMatrix<double,5>对象到一个期望获得SquareMatrix<double,10>的函数去。非常棒,但是必须付出代价。硬是绑着矩阵尺寸的那个invert版本,有可能生成比共享版本(其中尺寸乃以函数的参数传递或存储在对象内)更佳的代码。例如在尺寸专属版中,尺寸是个编译期常量,因此可以藉由常量的传达达到最优化,包括把它们折进被生成指令中成为直接常数。这在“与尺寸无关”的版本中时无法办到的。

另一个角度,不同大小的矩阵只拥有单一版本的invert,可减少执行文件大小,也就因此降低程序的工作集大小,并强化指令高速缓存区内的引用集中化。这些都可能使程序执行得更快速,超越“尺寸专属版”invert的最优化效果。哪一个占主导地位?欲知答案,唯一办法是两者都尝试并观察你的平台的行为以及面对代表性数据组时的行为。

   上面所说都是由非类型模板参数带来的代码膨胀,其实类型参数也会带来代码膨胀。例如在许多平台上int和long有相同的二进制表述,所以像vector<int>和vector<long>的成员函数有可能完全相同——这正是代码膨胀的最佳定义。某些链接器会合并完全相同的函数实现码,但有些不会,后者意味某些模板被具现化为int和long两个版本,并因此造成代码膨胀(在某些环境下)。类似情况,在大多数平台上,所有指针类型都有相同的二进制表述,因此凡模板持有指针者(例如list<int*>,list<const int*>,list<SquareMatrix<long,3>*>等等)往往应该对每一个成员函数使用唯一一份底层实现。这很具有代表性地意味,如果你实现某些成员函数而它们操作强型指针(即T*),你应该令它们调用另一个操作无类型指针(即void*)的函数,由后者完成实际工作。某些C++标准程序库实现版本的确为vector,deque和list等模板做了这件事。如果你关心你的模板可能出现代码膨胀,也许你会想让你的模板也做相同的事情。

 总结:(1)模板生成多个类和多个函数,所以任何模板代码都不该与某个造成膨胀的模板参数产生相依关系。

       (2)因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数或类成员变量替换template参数

       (3)因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同的二进制表述的具现类型共享实现码。

减少模板中的代码膨胀相关推荐

  1. C++拾趣——使用多态减少泛型带来的代码膨胀

    泛型编程是C++语言中一种非常重要的技术,它可以让我们大大减少相似代码编写量.有时候,我和同事提及该技术时,称它是"一种让编译器帮我们写代码的技术".(转载请指明出于breakso ...

  2. SupeSite模板中的代码代表什么意思

    SupeSite模版中的代码代表什么意思 supe_ads 广告表 adid smallint 广告id subject varchar 广告标题 adtype enum 广告类型 dateline ...

  3. 织梦模板中php代码,织梦模板内怎么加入php代码

    织梦模板内怎么加入php代码 织梦无忧 2020-09-10 09:21 摘要: 织梦模板支持php代码,虽然不能完全像写php页面那样,但是基本的东西还是够了. 一.模板页面内嵌入php 例如: { ...

  4. phpcms 模板中php代码,PHPCMS 模板制作教程 黑夜之舞出品

    第一讲:了解PHPCMS2008模板的位置及结构 首先从官网把phpcms2008最新版本下载下来,并安装好.安装好之后在后台里的网站配置--基本信息那 生成文件扩展名 html设置好,然后更新首页和 ...

  5. 计算几何模板中的代码

    计算几何模板代码选自kuangbin 7 计算几何 7.1 二维几何 // `计算几何模板` const double eps = 1e-8; const double inf = 1e20; con ...

  6. C++编程进阶6(public继承与组合、private继承、多重继承、处理模板基类内的名称、如何避免模板代码膨胀)

    二十一.public继承与组合 public继承是是子类对象is a基类对象的关系,比如QT中的所有组件类都要继承QObject,所以所有的QT组件都是一个QObject. 而组合是has a(包含) ...

  7. 番外篇--C++中的代码重用

    实现代码重用的一些方法(这里并不是全部): 包含(组合.层次化):类包含另一个类的对象 使用私有继承或保护继承 以上两种方法都用于实现has-a关系,常用第一种方法 多重继承可以使多个基类派生出一个新 ...

  8. [介绍] SymbolSort, 帮你避免或减少代码膨胀 (Code Bloat)

    代码膨胀?这在以前其实并不是我会记在心上的问题,最近由于发现某个工程生成的 DLL debug 版本居然达到 25M 之大,我就开始想怎么判断这是不是正常,如果不正常有办法解决吗?或者能否在以后多加注 ...

  9. Myeclipse学习总结(3)——Myeclipse中的代码格式化、注释模板及保存时自动格式化

    设置Myeclipse中的代码格式化.注释模板及保存时自动格式化 1:设置注释的模板: 下载此模板:  codetemplates.xml

最新文章

  1. [C] 层层递进——C语言实现广度优先搜索
  2. 判断线程是否执行完毕_关于线程池你不能不知道的东西
  3. 人脑计划:大脑研究如何对超级计算提出新要求
  4. 2019.03.10----LINUX学习笔记
  5. 行业观察(一)| 从渠道为王到数据为王——浅谈服装零售企业的数字化转型...
  6. javascript事件驱动框架 收藏
  7. 均值滤波器3*3模板_均值滤波器
  8. iOS UITableView的使用 (选自oschina)
  9. 富人有面子,穷人没面子的真相
  10. 如何安装oracle数据库
  11. BFS+模拟 ZOJ 3865 Superbot
  12. 7.java基本数据类型转换包含哪两类?
  13. chrome官网下载win64离线安装包
  14. Windows客户端开发--URLDownloadToFile下载文件进度条
  15. [每日一氵] 正则表达式以x开头,以x结尾的字符串
  16. 阵列信号处理笔记-波达方向DOA-子空间方法
  17. 中国共享汽车行业需求状况及发展前景预测报告(新版)2022-2027年
  18. CentOS 7的时间设置
  19. FlexRay总线原理及应用
  20. 操作系统原理总结,非科班必看!!!

热门文章

  1. GitHub 主页介绍及修改个人信息
  2. 2678v3支持内存频率_E52680V2与E52678V3的区别
  3. 清华毕业生拒绝开摄像头,全程怼面试官
  4. 谷歌使用技巧 20 招
  5. 免费馅饼的亿点小思路
  6. 这才是计算机科学_网络
  7. 图书管理系统-PHP大作业
  8. 还是经典的键盘,还是熟悉的味道:黑莓KEY2是否值得买?
  9. C++文件读取的四种情况
  10. 在华为云服务器上部署的web项目,外网不能访问处理方法