template是节省时间和避免重复代码的一个奇妙方法。class template的成员函数只有在被使用时才被暗中具现化。function templates有类似的诉求。

但是如果你不小心,使用templates可能导致代码膨胀(code bloat):其二进制代码带着重复(或几乎重复)的代码、数据、或两者。其结果可能源码看起来合身整齐,但目标码却不是那么回事。你需要知道如何避免这样的二进制浮夸。

主要工具是:共性与变性分析。

在non-template中,重复十分明确,然而在template中,重复是隐晦的:你必须训练自己去感受当template被具现化多次时可能发生的重复。

举个例子,矩阵template,支持矩阵inversion运算。

template<typename T, std::size_t n>
class SquareMatrix{
public:
    void invert();
};

template接受一个类型参数T,还有一个类型为size_t的非类型参数(non-type parameter)。

现在考虑:

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

这会具现两份invert。这些函数并非完全相同,但除了常量5和10,其他部分都相同,这是template引出代码膨胀的一个典型例子。

下面是对SquareMatrix的第一次修改:

template<typename T>                //与尺寸无关的base class
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);}    //制造一个inline调用,用this->为了不被derived classes的函数名称掩盖
};

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

目前为止一切都好,但是SquareMatrixBase::invert如何知道该操作什么数据?想必只有derived class知道。一个可能的做法是为SquareMatrixBase::invert添加一个新的参数,也许是个指针,指向一块用来放置矩阵数据的内存起始点。那行的通,但是十之八九invert不是唯一一个可写为“形式与尺寸无关并可移至SquareMatrixBase内”的SquareMatrix函数。如果有若干这样的函数,我们可以对所有这样的函数添加一个额外参数,却得一次次地告诉SquareMatrixBase相同的信息,这样不好。

另一个办法是令SquareMatrixBase贮存一个指针,指向矩阵数值所在的内存。而只要它存储了那些东西,也就可能存储矩阵尺寸:

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

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

template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<T>{
public:
    SquareMatrix()
        : SquareMatrixBase<T>(n, data){}
private:
    T data[n*n];

}

另一种做法是把每个矩阵的数据放进heap:

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

现在SquareMatrix<double, 5>和SquareMatrix<double, 10>有着不同的类型,即使使用相同的SquareMatrixBase<double>成员函数。

是的,很棒,但是必须付出代价。硬是绑着矩阵尺寸的那个invert版本,有可能生成比共享版本(其中尺寸乃以函数参数传递或存储在对象内)更佳的代码。例如在尺寸专属版本中,尺寸是个编译期常量,因此可以藉由常量的广传达到最优化,包括把它们折进被生成指令中成为直接操作数。这在“与尺寸无关”的版本中无法办到。

另一个角度看,不同大小大的矩阵只拥有单一版本的invert,可减少执行文件大小,因此降低working set(虚内存环境下执行进程所使用的那一组内存页)大小。并强化指令高速缓存区内的引用集中化(locality of reference)。这些都可能使程序执行的更快速,超越“尺寸专属版”invert的最优化效果。

另一个所关心的主题是对象的大小。如果将前述“与矩阵大小无关的函数版本”搬至base class内,这会增加每个对象的大小。每个SquareMatrix对象都有一个指针指向SquareMatrixBase class内的数据。虽然每个derived class已经有一种取得数据的办法,这会对每一个SquareMatrix对象至增加少一个指针那么大。当然可以拿掉这些指针,但是这其中需要若干取舍。

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

template生成多个class和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。

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

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

转载于:https://www.cnblogs.com/lidan/archive/2012/02/15/2353160.html

effective C++ 条款 44:将与参数无关的代码抽离templates相关推荐

  1. effective c++条款44 将与参数无关的代码抽离templates

    effective c++条款44 将与参数无关的代码抽离templates 首先了解这个条款的含义:使用template可能导致代码膨胀,二进制码会带着重复(或者几乎重复)的代码.数据,或两者.其结 ...

  2. Effective C++ 条款44

    本节条款的标题是:将与參数无关的代码抽离templates 学习本节条款首先须要明确一件事情,那就是模板实例化的过程会不会反复? 我们来举个样例: #include<iostream> u ...

  3. Effective C++条款(第三版-侯杰译)

    条款一:视C++为一个语言联邦 [C++高效编程守则视情况而变化,取决于你使用的C++哪一部分] 条款二:尽量以const,enum,inline替换#define [对于单纯变量,最好以const对 ...

  4. Effective C++条款粗略总结

    文章目录 Effective C++ 1.类/结构体 2.资源管理 3.实现 4.模板与泛型编程 5.定制new和delete 6.其他 Effective C++ 1.类/结构体 1.把C++看成一 ...

  5. effective c++条款11扩展——关于拷贝构造函数和赋值运算符

    effective c++条款11扩展--关于拷贝构造函数和赋值运算符 作者:冯明德 重点:包含动态分配成员的类 应提供拷贝构造函数,并重载"="赋值操作符. 以下讨论中将用到的例 ...

  6. Effective C++条款09:绝不在构造和析构过程中调用virtual函数

    Effective C++条款09:绝不在构造和析构过程中调用virtual函数(Never call virtual functions during construction or destruc ...

  7. Effective C++条款05:了解C++默默编写并调用哪些函数(Know what functions C++ silently writes and calls)

    Effective C++条款05:了解C++默默编写并调用哪些函数(Know what functions C++ silently writes and calls) 条款05:了解C++默默编写 ...

  8. Effective C++条款20:宁以pass-by-reference-to-const替换pass-by-value

    Effective C++条款20:宁以pass-by-reference-to-const替换pass-by-value(Prefer pass-by-reference-to-const to p ...

  9. Effective C++条款30:透彻了解inlining的里里外外(Understand the ins and outs of inlining)

    Effective C++条款30:透彻了解inlining的里里外外(Understand the ins and outs of inlining) 条款30:透彻了解inlining的里里外外 ...

  10. Perturbed Masking:和参数无关的预训练模型分析方法

    ©PaperWeekly 原创 · 作者|蔡杰 学校|北京大学硕士生 研究方向|问答系统 论文标题: Perturbed Masking: Parameter-free Probing for Ana ...

最新文章

  1. 如何破除“唯论文”?详解伯克利“科研重工业模式”的成功经验
  2. 边打“游戏”边学Vim!这款在线、交互的练习工具火了
  3. module 'tensorflow' has no attribute 'Session'
  4. c++程序员会用到的函数积累
  5. 弹出显示多条的message对话框
  6. curl的安装与简单使用
  7. Java异常分类及处理
  8. gtx1660是什么级别的_GTX1660Ti到底属于什么系列?Nvidia一句话定性了
  9. grub引导程序适用范围
  10. 玉溪推行电子政务 建设新型智慧城市
  11. mongodb导出csv文件到vcf
  12. 十大热门职位公布 高薪行业一目了然
  13. 怎么用计算机打印出东西,怎样在电脑上打印东西_怎么用电脑打印东西
  14. Camera ISO、快门、光圈、曝光
  15. 随机梯度下降法的数学基础
  16. Mapbox Android学习笔记(8)离线地图
  17. c语言综合程序设计省市邮政编码,《C语言程序设计课程设计报告》_课程教学大纲...
  18. pgsql处理文档类型数据_pgsql_pg的数据类型
  19. Python己亥杂说2 - 快排
  20. windows批处理小脚本总结

热门文章

  1. Caffe学习1-图像识别与数据可视化
  2. Anscombe's Quartet 问题
  3. driver nvidia web_黑苹果 macOS 10.13.6 17G66 安装 nVidia WebDriver
  4. 重定向与请求转发的区别
  5. 2022年 微信大数据挑战赛
  6. 《工业元宇宙白皮书 2022年 》(附免费pdf下载地址)
  7. “/etc/profile“ 无法保存 E212: Can‘t open file for writing
  8. Kubernetes 小白学习笔记(20)--kubernetes的运维-管理Node
  9. mysql5.5默认引擎,在MySQL5.5以上系统中,默认的存储引擎是( )。
  10. java监控文件内容变化_Java使用WatchService监控文件内容变化的示例