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

2023-11-10

        Templates是节省时间和避免代码重复的一个奇方妙法。不再需要键入20个类似的classes而每一个带有15个成员函数,你只需键入一个class template,留给编译器去具现化那20个你需要的相关classes和300个函数。(class templates的成员函数只有在被使用时才被暗中具现化,所以只有在这300个函数的每一个都被使用,你才会获得这300个函数。)Function templates有类似的诉求。替换写许多函数,你只需要写一个function templates,然后让编译器做剩余的事情。

        有时候,如果你不小心,使用templates可能会导致代码膨胀:其二进制码带着重复(或几乎重复)的代码、数据,或两者。其结果又可能源码看起来合身而整齐,但目标码(object code)却不是那么回事。

        当你编写某个函数,而你明白其中某些部分的实现码和另一个函数的实现码实质相同,你会很单纯地重复这些码码?当然不。你会抽出两个函数的共同部分,把它们放进第三个函数中,然后令原先两个函数调用这个新函数。也就是说,你分析了两个函数,找出共同的部分和变化的部分,把共同部分搬到一个新函数去,保留变化的部分在原函数中不动。

        编写templates时,也是做相同的分析,以相同的方式避免重复,但其中有个窍门。在non-template代码中,重复十分明确:你可以“看”到两个函数或两个classes之间有所重复。然而在template代码中,重复是隐晦的:毕竟只存在一份template源码,所以你必须训练自己去感受当template被具现化多次时可能发生的重复。

        举个例子,假设你想为固定尺寸的正方矩阵编写一个template。该矩阵的性质之一是支持逆矩阵运算。

template<typename T, std::size_t n>
class SquareMatrix {
public:
    ...
    void invert();        // 求逆矩阵
};

        这个template接受一个类型参数T,除此之外还接受一个类型为size_t的参数,那是个非类型参数。这种参数和类型参数比起来较不常见,但它们完全合法,而且就像本例一样,相当自然。

        现在,考虑这些代码:

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

        这回具现化两份invert。这些函数并非完完全全相同,因为其中一个操作的是5*5矩阵而另一个操作的是10*10矩阵,但除了常量5和10,两个函数的其他部分完全相同。这是template引出代码膨胀的一个典型例子。

        如果你看到两个函数完全相同,只除了一个使用5而另一个使用10,你会怎么做?你的本能会为它们建立一个带数值参数的函数,然后以5和10来调用这个带参数的函数,而不重复代码。下面是对SquareMatrix第一次修改:

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;见条款33
public:
    ...
    void invert() { this->invert(n); }    // 制造一个inline调用,调用base class版的invert。
};

        目前为止一切都好,但还有一些棘手的问题没有解决。SquareMatrixBase::invert如何知道该操作什么数据?虽然它从参数中知道矩阵尺寸,但它如何知道哪个特定矩阵的数据在哪儿?想必只有derived class知道。我们可以令SquareMatrixBase贮存一个指针,指向矩阵数值所在的内存。而只要它存储了那些东西,也就可能存储矩阵尺寸。成果看起来像这样:

template<typename T>
class SquareMatrixBase {
protected:
    SquareMatrixBase(std::size_t n, T* pMem)      // 存储矩阵大小和一个指针,指向矩阵数值
    : size(n), pData(pMem) {}
    void setDataPtr(T* ptr) { pData = ptr; }      // 重新赋值给pData
    ...
private:
    std::size_t size;                             // 矩阵大小
    T* pData;                                     // 指针,指向矩阵内容
};

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

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

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

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

        这个条款只讨论由non-type template parameters(非类型模板参数)带来的膨胀,其实type parameters(类型参数)也会导致膨胀。例如在许多平台上int和long有相同的二进制表述,所以像vector<int>和vector<long>的成员函数有可能完全相同——这正是最佳定义。

请记住

  • Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
  • 因非类型模板参数(non-type template parameters)而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数。
  • 因类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同的二进制表述的具现类型共享实现码。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将与参数无关的代码抽离templates——条款44 的相关文章

  • Effective C++

    条款01 视C 为一个语言联邦 将 视为一个由相关语言组成的联邦而非单一语言 条款02 尽量以const enum inline替换 define define处理与预处理阶段 而非编译阶段 因此此条款也可称为 宁可以编译器替换预处理器比较
  • Effective C++学习笔记——宁以传引用替换传值

    目录 一 传值效率可能会很低 二 传值可能发生割裂问题 三 适用于传值的情况和注意事项 相关博客 C 引用知识归纳 一 传值效率可能会很低 我们假设有这样两个类 class Human public string name string s
  • 编写new和delete时需固守常规——条款51

    条款50已解释什么时候你会想要写个自己的operator new和operator delete 但并没有解释当你那么做时必须遵守什么规则 这些规则不难奉行 但其中一些并不直观 所以知道它们究竟是些什么很重要 让我们从operator ne
  • 考虑写出一个不抛异常的swap函数——条款25

    swap是个有趣的函数 原本它只是STL的一部分 而后成为异常安全性编程 exception safe programing 见条款29 的脊柱 以及用来处理自我赋值可能性 见条款11 的一个常见机制 由于swap如此有用 适当的实现很重要
  • 区分接口继承和实现继承——条款34

    表面上直截了当的public继承概念 经过严密的检查之后 发现它由两部分组成 函数接口 function interfaces 继承和函数实现 function implementations 继承 这两种继承的差异 很像本书导读所讨论的函
  • 透彻了解inlining的里里外外——条款30

    Inline函数 多棒的点子 它们看起来像函数 动作像函数 比宏好得多 见条款2 可以调用它们又不需要蒙受函数调用所招致的额外开销 你还能要求更多吗 你实际获得的比想到的还多 因为 免除函数调用成本 只是故事的一部分而已 编译器最优化机制通
  • 令operator=返回一个reference to *this——条款10

    关于赋值 有趣的是你可以把它们写成连锁形式 int x y z x y z 15 同样有趣的是 赋值采用右结合律 所以上述连锁赋值被解析为 x y z 15 这里15先被赋值给z 然后其结果 更新后的z 再被赋值给y 然后其结果 更新后的y
  • 尽可能延后变量定义式的出现时间——条款26

    只要你定义了一个变量而其类型带有一个构造函数或析构函数 那么当程序控制流 control flow 到达这个变量定义式时 你便得承受构造成本 当这个变量离开其作用域时 你便得承受析构成本 即使这个变量最终未被使用 仍需耗费这些成本 所以你应
  • 在operator=中处理“自我赋值”——条款11

    自我赋值 发生在对象被赋值给自己时 class Widget Widget w w w 赋值给自己 这看起来有点愚蠢 但它合法 所以不要认定客户绝不会那么做 此外赋值动作并不总是那么可被一眼辨识出来 例如 a i a j 潜在的自我赋值 如
  • 尽量以const、enum、inline替换 #define——条款02

    这个条款或许改为 宁可以编译器替换预处理器 比较好 因为或许 define 不能被视为语言的一部分 一 比如定义一个宏 define ASPECT RATIO 1 653 这个ASPECT RATIO也许从未被编译器看见 也许在编译器开始处
  • 将文件间的编译依存关系降至最低——条款31

    假设你对C 程序的某个class实现文件做了些轻微修改 注意 修改的不是class接口 而是实现 而且只改private成分 然后重新建置这个程序 并预计只花数秒就好 毕竟只有一个class被修改 你按下 Build 按钮或键入make 或
  • 条款13: 以对象管理资源

    结论 为防止资源泄漏 请使用RAII对象 它们在构造函数中获得资源并在析构函数中释放资源 两个常被使用的RAII classes分别是tr1 share ptr和auto ptr 前者通常是较佳选择 因为其copy行为比较直观 若选择aut
  • Effective C++改善程序与设计的55个具体做法笔记

    Scott Meyers大师Effective三部曲 Effective C More Effective C Effective STL 这三本书出版已很多年 后来又出版了Effective Modern C More Effective
  • 《Effective C++》 全书内容提炼总结

    个人博客地址 https cxx001 gitee io 本文阅读说明 孔子云 取乎其上 得乎其中 取乎其中 得乎其下 取乎其下 则无所得矣 对于读书求知而言 这句古训教我们去读好书 最好是好书中的上品 经典书 Effective C 就是
  • Effective C++ 学习笔记 《六》

    Item 6 Explicitly disallow the use of compiler generated functions you do not want 其实这一节的内容是和item5紧密相连的 上一节的核心围绕着编译器会自动生
  • 考虑virtual函数以外的其他选择——条款35

    假设你正在写一个视频游戏软件 你打算为游戏内的人物设计一个继承体系 你的游戏术语暴力砍杀类型 剧中人物被伤害或因其他因素而降低健康状态的情况并不罕见 你因此决定提供一个成员函数healthValue 它会返回一个整数 表示人物的健康程度 由
  • 写了placement new也要写placement delete——条款52

    placement new和placement delete并非C 兽栏中最常见的动物 如果你不熟悉它们 不要感到挫折或忧虑 回忆条款16和17 当你写一个new表达式像这样 Widget pw new Widget 共有两个函数被调用 一
  • 明智而审慎地使用多重继承——条款40

    当多重继承 multiple inheritance MI 进入设计景框 程序有可能从一个以上的base classes继承相同名称 如函数 typedef等等 那会导致较多的歧义机会 例如 class BorrowableItem 图书馆
  • 复制对象时勿忘其每一个成分——条款12

    设计良好之面向对象系统 OO systems 会将对象的内部封装起来 只留两个函数负责对象拷贝 复制 那便是带着适切名称的copy构造函数和copy assignment操作符 我称它们为copying函数 条款5观察到编译器会在必要的时候
  • Effective C++——尽可能使用const

    const允许指定一个语义约束 也就是指定一个 不该被改动 的对象 而编译器会强制实施这项约束 只要保持某个值不变是事实 就应该说出来 以获得编译器的协助 保证不被违反 const与指针 注意const的写法 const char p p可

随机推荐

  • Java实现二叉树的遍历(递归和非递归)

    现有一颗如下图所示的二叉树 一 基本概念 1 先序遍历 深度优先遍历 前 中 后这三个词是针对根节点的访问顺序而言的 先访问根结点 再访问左子结点 最后访问右子结点 图中的二叉树的先序遍历的顺序是1 2 4 8 9 5 3 6 7 2 中序
  • DHCP详解

    DHCP简介 我们都知道一台计算机或者手机想要上网 必须要有一个IP地址 要不然别人是找不到你的 如果我们要手动配置IP的话 是非常麻烦的 因为一个IP地址要对应着一个网络 而一个网络对应一个位置 如果主机更改位置了 要重新变换IP地址 实
  • ERROR: Could not find a version that satisfies the requirement setuptools_scm (from versions: none)

    一 项目场景 在使用百度飞桨导入paddlehub包时 一直出现没有 paddlehub 包的错误 换了好几个镜像源都不行 出现以下错误 WARNING The repository located at pypi douban com i
  • vue 实现Tabs 组件自定义删除+拖拽排序功能

    前言 目前市面上有很多实现拖拽排序功能的插件和方法 本节不过多累述 只讲一种 vue的v dragging内置组件 效果图 主图 拖拽中的图 1 安装 npm install awe dnd save 2 在 main js 文件中引入 i
  • 《算法图解》高清PDF版

    算法图解 高清PDF版 像小说一样好看容易理解的算法书籍 适合算法和竞赛入门者学习 书中的示例代码是python width 738 height 523 class preview iframe scrolling no src http
  • Flutter GetX使用详细解读

    FlutterGetX 是一个基于 Flutter 框架的状态管理和依赖注入库 它与其他状态管理库相比 具有以下优势 简单易用 FlutterGetX 采用简单明了的 API 设计 易于学习和使用 高性能 FlutterGetX 的状态更新
  • Android 学习之环境变量配置以及无法安装 intel HAXM问题的解决

    前提 已完成Java运行环境的全部配置 1 Android 开发工具Android Studio的下载和安装 自行百度 2 下载Android SDK以及设置环境变量 2 1 Android SDK 开发工具中可搜索下载 2 2环境变量配置
  • 02-像元大小

    https blog csdn net peckerzeng article details 78319935 在解释像元大小 Cell size of raster data 的概念的时候 我们有必要先引入另外一个名词叫做 像素 像素顾名
  • python最小二乘法拟合模型的loocc误差_最小二乘法拟合+3sigema去除误差大的点

    for i 1 96 for j 1 96 xdata 4 8 12 16 20 24 28 32 if ratio1 i j ratio2 i j ratio3 i j ratio4 i j ratio5 i j ratio6 i j r
  • C++模板template用法

    引言 模板 Template 指C 程序设计设计语言中采用类型作为参数的程序设计 支持通用程序设计 C 的标准库提供许多有用的函数大多结合了模板的观念 如STL以及IO Stream 1 模板 1 1 什么是函数模板 函数模板定义一族函数
  • Linux·内核的 4 大 IO 调度算法

    Linux 内核包含4个IO调度器 分别是 Noop IO scheduler Anticipatory IO scheduler Deadline IO scheduler 与 CFQ IO scheduler anticipatory
  • 资源编排

    ROSTemplateFormatVersion 2015 09 01 Parameters InstanceName Description 镜像名称 Type String Default localhost Resources Web
  • vben admin 之语言配置、使用、切换

    思路 语言导入逻辑 初始化 在 src locales setupI18n 内的根语言文件中 import lang locale ts import type App from vue import type I18n I18nOptio
  • 共模电感的工作原理

    共模电感的工作原理 在电路设计时 经常会听到利用共模电感来抑制电源噪声 但对共模电感的工作原理却不是很理解 在看了几篇文章后才有了比较清晰的认识 特此总结起来以增强认知和理解 共模电感 指在某种磁性材料的磁环上绕上同向的一对线圈 差模电流
  • 设计分享

    目录 具体实现功能 设计介绍 51单片简介 设计思路 设计内容 仿真图 protues8 7 程序 KeilC51 具体实现功能 利用滑动变阻器实现对直流电机转速的控制 仿真实现 汇编语言编写 设计介绍 51单片简介 51单片是一种低功耗
  • 【SCCB接口协议简介(适用于OV系列摄像头)】

    SCCB总线简介 SCCB协议与IIC协议十分相似 不过IIC是PHILIPS的专利 所以OmnVision在IIC的基础上做了点小改动 SCCB最主要是阉割了IIC的连续读写的功能 即每读写完一个字节就主机必须发送一个NA信号 SCCB简
  • 场效应管(MOS)基础知识

    MOSFET管是常用的半导体器件 又称为开关管 场效应管 英文名称 MOSFET 简称MOS管 按元件封装工艺可分为两大类 插件类 贴片类 大部分MOSFET管的外观极其类似 常见的封装种类有T0 252 T0 251 T0 220 T0
  • 新年手打,24道进阶必备Elasticsearch 面试真题(建议收藏!)

    1 elasticsearch 了解多少 说说你们公司 es 的集群架构 索 引数据大小 分片有多少 以及一些调优手段 面试官 想了解应聘者之前公司接触的 ES 使用场景 规模 有没有做过比较大 规模的索引设计 规划 调优 解答 如实结合自
  • JavaScript修改Css样式

    在JS中操作CSS属性命名上的区别 以前css直接写死在html中 现在可以通过js脚本去动态修改一个标签的样式 CSS中写法 JS中的写法 说明 color color 一个单词的样式写法是相同 font size fontSize 驼峰
  • 将与参数无关的代码抽离templates——条款44

    Templates是节省时间和避免代码重复的一个奇方妙法 不再需要键入20个类似的classes而每一个带有15个成员函数 你只需键入一个class template 留给编译器去具现化那20个你需要的相关classes和300个函数 cl