EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)

EffectiveC++

    • 简介
    • 项目链接
    • 一. 让自己习惯C++
      • 条款01: 视C++为一个语言联邦
      • 条款02: 尽量以 const, enum,inline 替换 #define
      • 条款03: 尽可能使用 const
      • 条款04: 确定对象被使用前已经被初始化
    • 二. 构造/析构/赋值运算
      • 条款05: 了解C++默认调用哪些函数
      • 条款06: 若不想使用编译器自动生成的函数,就该明确拒绝
      • 条款07: 为多态基类声明 virtual 析构函数
      • 条款08: 别让异常逃离析构函数
      • 条款09: 绝不在构造和析构过程中调用 virtual 函数
      • 条款10: 令 operator= 返回一个 reference to *this
      • 条款11: 在 operator= 中处理 “自我赋值”
      • 条款12:复制对象时勿忘其每一个成分
    • 三. 资源管理
      • 条款13: 以对象管理资源
      • 条款14: 在资源管理类中小心 copying 行为
      • 条款15: 在资源管理类中提供对原始资源的访问
      • 条款16: 成对使用 new 和 delete 时要采取相同形式
      • 条款17: 以独立语句将 newed 对象置入智能指针
    • 四. 设计与声明
      • 条款18: 让接口容易被正确使用, 不易被误用
      • 条款19: 设计 class 犹如设计 type
      • 条款20: 宁以 pass-by-reference-to-const 替换 pass-by-value
      • 条款21: 必须返回对象时,别妄想返回其 reference
      • 条款22: 将成员变量声明为 private
      • 条款23: 宁以non-member,non-friend 替换 member 函数
      • 条款24: 若所有参数皆需类型转换,请为此采用 non-member 函数
      • 条款25: 考虑写出一个不抛异常的 swap 函数
    • 五. 实现
      • 条款26: 尽可能延后变量定义式的出现时间
      • 条款27: 尽量少做转型动作
      • 条款28: 避免返回 handles 指向对象内部成分
      • 条款29: 为 “异常安全” 而努力是值得的
      • 条款30: 透彻了解 inlining 的里里外外
      • 条款31: 将文件间的编译依存关系降至最低
    • 六. 继承与面向对象设计
      • 条款32: 确定你的 public 继承素模出 is-a 关系
      • 条款33:避免遮掩继承而来的名称
      • 条款34: 区分接口继承和实现继承
      • 条款35: 考虑 virtual 函数以外的其他选择
      • 条款36: 绝不重新定义继承而来的 non-virtual 函数
      • 条款37: 绝不重新定义继承而来的缺省参数值
      • 条款38: 通过复合塑模出 has-a 或 “根据某物实现出”
      • 条款39: 明智而审慎地使用 private 继承
      • 条款40: 明智而审慎地使用多重继承
    • 七. 模板与泛型编程
      • 条款41:了解隐式接口和编译期多态
      • 条款42:了解 typename 的双重意义
      • 条款43: 学习处理模板化基类内的名称
      • 条款44: 将与参数无关的代码抽离 templates
      • 条款45: 运用成员函数模板接受所有兼容类型
      • 条款46: 需要类型转换时请为模板定义非成员函数
      • 条款47: 请使用 traits classes 表现类型信息
      • 条款48: 认识 template 元编程
    • 八. 定制 new 与 delete
      • 条款49: 了解 new-handler 的行为
      • 条款50: 了解 new 和 delete 的合理替换时机
      • 条款51: 编写 new 和 delete 时需固守常规
      • 条款52: 写了 placement new 也要写 placement delete
    • 九. 杂项讨论
      • 条款53: 不要忽视编译器的警告
      • 条款54:让自己熟悉 TR1 在内的标准程序库
      • 条款55: 让自己熟悉 Boost





简介

effectiveC++ 这本书是C++程序员的工作必备之书,讲述了在C++开发中常用的一些,以及经常注意的一些规则,遵循它且不要忽视它,我们就能写出很好的友善的C++代码。



EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第1张图片



项目链接

点击下载: github项目链接


一. 让自己习惯C++


条款01: 视C++为一个语言联邦

C++是一个多重范型编程语言(Multiparadigm programing language)

  1. 支持过程形式
  2. 面向对象形式
  3. 函数形式
  4. 泛型形式
  5. 元编程形式

我们理解其C++时应该视其为 一个相关语言组成的联邦(有4个次语言)

  1. C:对于C++问题的解法类似C的高级解法时,高效的解法就是去映射C语言的规范,不要掺杂过多C++其他此语言特性
  2. Object-Oriented C++: 这部分即 C With Class,C++的主流编程
  3. Template C++: 十分强大的模板编程范式
  4. STL 模板库: STL 对于容器迭代器算法函数对象等等的规约有极佳的紧密配合与协调,伙同STL进行开发,要遵循其规约

C++对于该4个次语言都有它自己的规约, 记住这4个次语言你就会发现C++容易了解的多

  • 建议: C++高校编程守则视情况而变化,取决于你使用C++的哪一个部分。


条款02: 尽量以 const, enum,inline 替换 #define

请看以下代码:

#define ASPECT_RATIO 1.653
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

ASPECT_RATIO 从未被编译器看到,而是被预处理器展开, 可能出现的问题:

  1. 运用该宏定义的常量时出现了编译错误信息时,错误提示提到的是 1.653 而非常量名,排查错误难
  2. 普通的变量会被编译器看到,会记录到记号表中(symbol table) ,而宏定义的常量不会被记录,导致出现
    常量值的目标码(object code),从而可能造成代码膨胀(代码膨胀会导致额外的换页行为,降低指令高速缓存装置的击中率,带来效率损失)。
  3. 函数宏虽不会有函数调用的栈方面的开销,但是缺点很明显,难读,容易出错

一. 以 const 替换 宏定义常量
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第2张图片

二. 以enum hack 替换 宏定义常量
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第3张图片

三. 以 inline 替换 宏定义函数

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第4张图片

总结

  1. 对于单纯常量, 最好以 const 对象或 enums 替换 #defines
  2. 对于形似函数的宏, 最好改用 inline函数替换 #define


条款03: 尽可能使用 const

一. const 是C++中的对于变量语义约束(不可修改),编译器会强制实行这项约束,只要该值不可被改变(事实),就应该去进行约束

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第5张图片
二. const 最具威力的是面对函数声明时的应用, const可以与函数返回值,各参数,函数自身(成员函数)产生关联

  • 令函数返回一个常量值,可以降低因客户操作而造成的(意外错误)
    EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第6张图片
  • const 实施成员函数: 确认该成员函数可作用于 const 对象身上。
    第一:使得 class接口更加容易被理解,(得知那些函数可以改动对象内容那些不行)
    第二:使得操作 const 对象成为可能, (pass by reference-to-const 方式传递对象),我们有const成员函数处理const对象。

成员函数如果只是常量性不同,可以被重载
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第7张图片

如何实施对对象进行const限制的措施:

  1. bitwise const阵营:不更改对象内的任何一个 bit (太过于强硬)
  2. bitwise constness阵营: 对对象的成员变量实施 const(编译器的做法)
  3. logical constness(重要): 一个const成员函数可以修改它所处理的对象的某些 bits,但请确保客户端侦测不出 (实现办法是利用C++的一个与const相关的摆动场: mutable)

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第8张图片

在const 和 non-const 成员函数中避免重复(写出重复的代码)
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第9张图片

总结:

  1. 将某些东西声明为const能帮助编译器去甄别错误用法
    (作用域对象,函数参数,函数返回类型,成员函数本体)
  2. 编译器强制实施 bitwise constness,但我们编写程序时应该使用 “概念上的常量性”
  3. 当const 和 non-const成员函数有着实质等价的实现时,令 non-const 版本调用const版本可避免代码重复


条款04: 确定对象被使用前已经被初始化

一. C++的对象成员变量的初始化发生在进入构造函数本体之前

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第10张图片

如上代码会对成员先进行默认构造函数的调用,之后在进行赋值。

解决办法是: 使用 member initalization list 初始化列表替换赋值动作。 为了规范统一:将全部成员(无物也要使用初始化列表初始化)

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第11张图片
注意:C++类对象的初始化次序: base class 总是早于其 derived class 被初始化, class 的成员变量总是以其声明次序被初始化

二. C++对于定义在不同编译单元内的non-local static 对象的初始化相对次序无明确定义。

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第12张图片

解决办法就是使用 Singleton 使得 non-local static 搬到自己的专属函数中
C++保证, 函数内的 local static对象会在 “首次遇上该对象之定义上” 被初始化。

多线程环境下执行顺序的麻烦性: 尽量在单线程运行期按一定顺序初始化这些 static

在这里插入图片描述

总结:

  1. 为内置型对象进行手工初始化,因为 C++不保证初始化他们
  2. 构造函数最好使用成员初始值,而不要在构造函数本体内使用赋值操作, 且初始
    列列出的成员变量,其排序次序应该和他们在 class 中声明次序相同
  3. 为免除“跨编译单元之初始化次序”问题, 请以 local static 对象替换 non-static 对象


二. 构造/析构/赋值运算


条款05: 了解C++默认调用哪些函数

一. 检阅一个 empty 类编译器为其做的事情

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第13张图片

二: 编译器合成的函数做的事情

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第14张图片

发现: operator= 与 copy构造函数 都是编译器合成, 内置类型的成员使用拷贝bit方式,非内置则调用 其定义的 operator= 与 copy构造函数从右侧操作数拷贝数据

三. 注意点

  1. 默认拷贝赋值运算符/拷贝构造函数在成员含引用类型时不能被生成
  2. 当基类将拷贝赋值运算符/拷贝构造函数声明为 private或 delete, 也是不能被生成的。

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第15张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第16张图片

总结

编译器可以暗自为class 创建default构造函数,copy构造函数,copy assignment 操作符,以及析构函数。


条款06: 若不想使用编译器自动生成的函数,就该明确拒绝

一. 最简单的拒绝(copy构造函数与 copy赋值运算符)办法是 声明其为 private

  1. 为了解决在成员函数和友元函数仍然还是能调用,声明其而不定义其是个好办法(会报链接错误)
  2. 为了将链接错误提前到编译期,需要将其继承一个阻止 copy 的 base class(利用了继承了基类的拷贝操作为private的类,编译器自身将不会生成其 拷贝操作,使用时就会报错)

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第17张图片


条款07: 为多态基类声明 virtual 析构函数

一:问题浮现: 销毁一个heap分配的基类指针(指向的是派生类)内存泄漏问题
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第18张图片
原因: 通过GetTimeKeeper 返回的指针是一个基类指针,销毁基类指针则会取基类的部分(调用基类的析构函数)

官方: C++明白指出,当derived class对象经由一个base class指针被删除,而其base class带一个 non-virtual函数, 其结果就是未定义-实际执行下来发生的就是对象的 derived 成分没被销毁

解决: 给base class 设置一个 virtual 析构函数即可

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第19张图片

二: 验证: 任何 class 带有virtual函数都几乎确定应该有一个 virtual 析构函数, 没有理由地把所有 class 的析构函数设置为 virtual的行为是错误的。

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第20张图片

三: 利用析构函数实现抽象类, 适用于没有其余能定义pure virtual函数的类

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第21张图片

总结

  1. polymorphic base classes 应该声明一个virtual析构函数, 如果 class 带有任何 virtual 函数,他就应该拥有一个 virtual 析构函数

  2. Class 的设计目的如果不是当作 base classes 使用,就不应该声明 virtual 析构函数


条款08: 别让异常逃离析构函数

首先C++并不禁止析构函数抛出异常,但在析构函数中抛出异常很容易导致内存泄漏(程序过早结束)

一: 验证析构函数抛出异常的问题

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第22张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第23张图片

二: 使用最佳策略解决该问题,避免析构函数传播异常

我们要对 “导致 close 抛出异常” 的情况做出反应

重新设计 DBConn接口,使客户对有机会对可能出现的问题作出反应

1: 管理类提供一个 close 函数,赋予客户一个机会处理因该操作而发生的异常。
2: 管理类设置标志位并在析构函数调用时检测其是否正常关闭,如果未关闭,则正常关闭
3: 第二步在析构函数种再次关闭失败后,我们将又退回 “强迫结束程序或吞下异常的套路”

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第24张图片

总结:

  1. 析构函数绝不要吐出异常, 如果一个被析构函数调用的函数可能抛出异常,析构函数
    应该捕捉任何异常, 然后吞下他们(不传播)或结束程序。

  2. 如果客户端需要对某个操作函数运行期间抛出的异常做出反应,那么 class 应该提供一个 普通函数(而非在析构函数种)执行该操作


条款09: 绝不在构造和析构过程中调用 virtual 函数

一: 证明在 base class 构造期间, virtual 函数不是 virtual 函数

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第25张图片
原因1: 如果在构造base class时调用的是 derived class 的函数(会使用到derived class成员, 但此时成员都是未构造的,会出现问题)。

原因2: 在derived class对象的 base class构造期间,对象本身是base class而不是 derived class不止virtual函数会被编译器解析至 base class,运行期类型信息,也会把对象视为 base class 。

相对应析构函数执行到base class部分,编译器也会视当前对象为 base class

二:如何确保每一次有 Transaction继承体系上的对象被创建,就会有适当版本的 logTransaction被调用

由于你无法在 base class构造时期通过 virtual函数 调用到 derived class 的函数,
因此可以使用 非virtual 通过在 derived class构造函数传(必要参数)传递到 base class 的构造函数, 进而调用 base class 的通过必要参数而实行的普通函数

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第26张图片

总结

  1. 在base class 构造和析构期间不要调用 virtual 函数,因为这类调用从不下降至 derived class
    (比起当前执行构造函数和析构函数那层),virtual本质上并没有用

条款10: 令 operator= 返回一个 reference to *this

这份协议可以说是为了实现连锁赋值而 创造的协议

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第27张图片

总结:

连锁赋值几乎被所有内置类型和标准库程序提供的类型, string,vector都遵守,因此
我们自定义的也最好共同遵守

条款11: 在 operator= 中处理 “自我赋值”

自赋值是十分没有必要的,但也是代码漏点很多的一个问题

  1. 类中有heap资源, 编写代码时需要注意 异常安全性
  2. 自赋值的避免

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第28张图片
总结

确保当对象自我赋值时 operator= 有良好的行为(鲁棒性,通用性更强),其中技术包括

  1. 来源对象和目标对象的地址
  2. 精心周到的语句顺序
  3. copy and swap

确保任何函数如果操作一个以上的对象,且多个对象都是同一个对象时,其行为仍然正确

条款12:复制对象时勿忘其每一个成分

如果拒绝编译器自动生成copying函数,如果你的代码不完全,他们也不告诉你!!!

一: 局部拷贝的错误

这里如果没有复制新添加的变量,编译器也是不会进行提醒的,因为你已经拒绝编译器进行为你生成 copy 函数
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第29张图片

实验二: 继承拷贝的错误

继承一个基类后进行编写派生类的 copy 函数时应注意编译器不会进行对 派生类的基类部分自动拷贝
需要手写基类部分的复制

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第30张图片

总结

  1. copying 函数应该确保复制 ”对象内的所有成员变量及所有 base class 成分“
  2. 不要尝试以某个 copying 函数实现另一个 copying函数,应该将共同机能放进第三个函数中
    并由两个 copying 函数共同调用

三. 资源管理

条款13: 以对象管理资源

基于对象的资源管理办法,是十分有效的

一: 体验基于对象资源管理的方法

自行编写的写法

  1. 获得资源后立即放入管理对象内, ”以资源取得时机便是初始化时机“ (RAII)
  2. 管理对象运用析构函数确保资源被释放。 (不论控制流如何,只要对象被销毁,析构函数必会调用)在析构函数中有异常,请遵循条款8

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第31张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第32张图片
测试:
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第33张图片

总结

  1. 为了防止资源泄漏,请使用 RAII对象, 他们在构造函数中获得资源并在析构函数中释放资源
  2. 两个常被使用的 RAII 对象 classes 分别是 std::shared_ptr 和 std::unique_ptr
    shared_ptr 是一个 RCSP(reference count smart pointer)

条款14: 在资源管理类中小心 copying 行为

资源的类型并非都是 heap+based 资源时,你需要建立自己的资源管理类

一: 体验资源管理类中copying行为带来的问题

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第34张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第35张图片

面对资源 copy 的动作有如下解决方案

  1. 禁止拷贝
  2. 对底层资源使用 RCSP ,引用计数法 (使用 shared_ptr)
  3. 深拷贝复制底部资源
  4. 转移底部资源的拥有权

二: 禁止 copy 行为

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第36张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第37张图片

三: 引用计数 RCSP
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第38张图片

总结

  1. 复制 RAII 对象必须一并复制它所管理的资源,资源的copying行为决定RAII对象的copying行为
  2. 普遍而常见的 RAII class copying行为是: 抑制copying,施行引用计数

条款15: 在资源管理类中提供对原始资源的访问

提供原始资源的访问以方便客户

例如: shared_ptr 的get获取原始指针

  1. 显式返回原始指针
  2. 隐式直接进行转换(在类中定义 转换资源类型的 运算符)

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第39张图片

测试:
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第40张图片

总结

  1. APIs 往往要求访问原始资源,所以每个 RAII class 应该提供一个
    ”取得其所管理之资源“ 的办法
  2. 对原始资源的访问可能经由显示转换或隐式转换。一般而言显示转换比较安全,
    但隐式转换对客户比较方便。

条款16: 成对使用 new 和 delete 时要采取相同形式

  1. new 对应 delete, new[] 对应 delete[]
  2. 尽量少使用数组,多使用 vector string 标准库

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第41张图片

条款17: 以独立语句将 newed 对象置入智能指针

一: 我们来演示一个复杂的错误(异常导致内存泄漏)

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第42张图片

c++ 语言的函数传参调用顺序弹性很大
可能会出现调用顺序如下的表现:

  1. new Widget
  2. priority() //假若这里抛出了异常,就会有内存泄漏问题
  3. shared_ptr

更好的办法

在这里插入图片描述
总结

  1. 尽量以独立的语句将 heap 资源置入 smart pointer

四. 设计与声明

条款18: 让接口容易被正确使用, 不易被误用

理想上,如果客户企图使用某个接口而却没有获得他预期的行为,这个代码不能通过编译

一:明智而审视地导入新类型对于预防“接口被误用” 有神奇疗效

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第43张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第44张图片

二: 以函数替换对象,预先定义有效的 对象使得接口更具备安全性

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第45张图片

另外: 除非有非常好的理由,否则应令 type 与 内置 type 提供一致的行为接口。

总结:

  1. 好的接口很容易被正常使用,不容易被误用
  2. “促进正确使用”的办法包括接口的一致性, 以及与内置类型的行为兼容
  3. “阻止误用” 的办法包括建立新类型, 限制类型上的操作,束缚对象值, 以及消除客户的资源管理责任
  4. shared_ptr 支持定制型删除其,可防范在不同DLL间出现的引用计数的问题,可被用来自动解除互斥锁等等资源问题

条款19: 设计 class 犹如设计 type

如何设计高效的 classes 呢, 遵循问题产出设计规范:

  1. 新的type的对象应该如何被创建和销毁? (构造函数析构函数)
  2. 对象的初始化和对象的复制该有什么样的差别? (构造函数与拷贝赋值运算符)
  3. 新type的对象如果被passed by value(以值传递),意味着什么? (copy构造函数)
  4. 什么是新typed “合法值”? (维护约束条件,要在构造,赋值,setter函数进行错误检查)
  5. 你的新 type 需要配合某个继承图系? (virtual 与 non-virtual的影响, 特别是析构函数 virtual)
  6. 你的新 type 需要什么类型的转换? (explict 与 non-explict,以及隐式转换运算符定义)
  7. 什么样的操作符和函数对该新type而言是合理的? (声明哪些函数, memeber函数还是否)
  8. 什么样的标准函数应该被驳回? ( 编译器自动生成的那些声明其为 delete)
  9. 谁该取用新的 type 的成员? (存取函数进行约束)
  10. 什么是新的 type 的“未声明接口”? ()
  11. 你的新 type 有多么一般化? (template 或 一整个types家族)
  12. 你真的需要一个新type吗? (如果只是为了扩充功能而进行派生,倒不如直接定义一个 non-member函数)

条款20: 宁以 pass-by-reference-to-const 替换 pass-by-value

一: 使用引用类型能提升效率问题
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第46张图片
二:使用引用进行传参可以实现多态且避免对象切割问题

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第47张图片

总结

  1. 尽量以 pass by reference, 即高效也支持多态,避免切割问题
  2. 内置类型以及 STL 迭代器和函数对象,使用 pass by value 比较适当,(属于c语言块的内容以c语言方式进行处理)

条款21: 必须返回对象时,别妄想返回其 reference

一: 拒绝返回局部作用域的局部变量的引用,那其实指向了一片不存在的区域

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第48张图片

总结:

  1. 绝对不要返回 pointer 或 reference 指向的一个 local stack 对象,或者返回引用指向一个
    heap-allocated 对象,或返回 pointer 或 reference 指向一个 local static对象而有可能同时
    需要多个这样的对象

条款22: 将成员变量声明为 private

一: 如何对成员变量进行有效的控制

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第49张图片

总结:

  1. 切记将成员变量设置为 private,这可赋予客户访问数据的一致性,可细微划分访问控制,允许约束条件 得到保证,并提供 class 作者以充分的实现弹性。

  2. 仅存在两种访问权限: private(提供封装)和 其他(不提供封装),protected 并不比 public 更具封装性。

条款23: 宁以non-member,non-friend 替换 member 函数

一: 在许多情况下 非成员非友元函数的做法比 member 好得多。

  1. 面向对象守则要求数据尽可能被封装,member函数封装性比 non-member 函数差,且需要重新编译整个类与相关联的,因为不想调用到该函数的实例对象也能调用到,但如果是非成员版本就是谁能用谁调用.
  2. 愈多东西被封装,我们改变那些东西的能力就越大,越方便,因为涉及到有限的客户代码

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第50张图片
二: C++ 对于命名空间的妙用,适用 non-member,non-friend

将所有便利函数防止多个头文件内但隶属于同一个命名空间,意味着客户可以轻松扩展这一组便利函数,他们需要做的就是添加更多 non-member non-frient 函数到此命名空间内。
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第51张图片

总结

  1. 宁可拿 non-member non-friend 函数替换member函数,这样做可能增加封装性,包裹弹性
    和机能扩充性。

条款24: 若所有参数皆需类型转换,请为此采用 non-member 函数

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第52张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第53张图片
总结

  1. 若重载运算符函数的操作数皆需类型转换,就声明其为非成员函数

条款25: 考虑写出一个不抛异常的 swap 函数

一:. 实现一个 pimpl 手法的类, 并写出其 特例化的swap 函数

在这里插入图片描述
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第54张图片

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第55张图片

总结:

  1. 提供一个 public swap 成员函数,让它高效地置换你的类型的两个对象值,这个函数不能抛出异常
  2. 在你的 class 或 template 所在的命名空间内提供一个 non-member swap,并令它调用上述 swap 成员函数
  3. 如果你正编写一个 class, 为你的class 特化 std::swap ,并令它调用你的 swap 成员函数
  4. 最后调用 swap 时,确保包含一个 using 声明符,以便让 std::swap 在你的函数曝光
    然后不加任何 namespace 修饰符, 赤裸裸地调用 swap。
  5. 注意:不能抛出异常,因为swap是帮助 class 提供异常安全性的保障, 基于的条件就是 swap不能抛出异常

五. 实现

条款26: 尽可能延后变量定义式的出现时间

尽可能延后的真正意义
1: 不仅只是延后变量的定义,直到非带使用该变量的前一刻为止,甚至
应该尝试延后这份定义直到能够给它初值实参为止。
2. 不仅能够避免构造(析构)非必要对象,还能避免无意义的default的构造行为。

一: 1: 尽可能延后能优化代码

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第56张图片

二:循环中所使用变量需不需要延后,还是提前定义?

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第57张图片

总结
尽可能延后变量定义的出现,这样做可以增加程序的清晰度并改善程序效率。

条款27: 尽量少做转型动作

const_cast() //去除添加 const常量性(一般用于引用)
dynamic_cast() //动态类型转换,将指向派生类对象的基类指针(引用)转换为派生类指针(引用)
reinterpret_cast() //不可移植行为类似 c 语言转
static_cast() // 静态类型转换(隐式转换显示表现)

演示使用

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第58张图片

一: 派生类的virtual 动作先调用 base 的对应函数,容易出现的错误

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第59张图片

二: 更容易避免使用 dynamic_cast 的两种方法

普遍的实现版本基于 class 名称之字符串比较, 当深度继承时,其strcmp就会变多,因此注重效率的代码应该 对 dynamic_cast 保持机敏和猜疑

之所以使用 dynamic_cast, 通常是因为你想在你认定为 derived_class 对象身上执行 derived class 操作函数,但你手上仅有一个 指向 base 的 pointer 或 reference

  1. 直接使用容器存储 指向 derived class 对象的指针(比较不切实际)
  2. base class 提供 virtual 函数做你想对各个 Window 派生类做的事情,使用多态性质即可 (推荐)

在这里插入图片描述

直接使用容器存储 指向 derived class 对象的指针(比较不切实际)

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第60张图片

base class 提供 virtual 函数做你想对各个 Window 派生类做的事情,使用多态性质即可 (推荐)
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第61张图片

总结:

  1. 如果可以,请避免转型,特别是注重效率的代码中避免 dynamic_casts,如果有个
    设计需要转型动作,请试着发展无须转型的替代设计
  2. 如果转型是必要的,试着将它隐藏于某个函数背后, 客户随后可以调用该函数, 而不需要
    将转型放在他们的代码中, othertype ObjectToOther(object &obj)
  3. 宁可使用C++ style(新式)转型, 不要使用旧式转型, 前者很容易辨识出来,而且也有比较分门别类的职掌

条款28: 避免返回 handles 指向对象内部成分

handles(指针,引用,迭代器)

一: 探索返回对象内部成分带来的弊端

  1. 破坏封装性
  2. 导致空悬指针等情况(临时对象的内部竟然能被指向)
    EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第62张图片
    EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第63张图片

总结

  1. 避免返回 handles(ref, 指针,迭代器)指向对象内部,增加封装性,且帮助 const 成员函数像一个 const,减少 空悬 的发生(临时对象的内部竟然能被指向

条款29: 为 “异常安全” 而努力是值得的

异常抛出时,带有异常安全性的函数会

  1. 不泄露任何资源 2. 不允许数据败坏

异常安全性提供以下三个保证之一:

  1. 基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。
    没有任何对象或数据结构因此而败坏,所有对象都处于一种内部前后一致的状态

  2. 强烈保证:如果异常被抛出, 程序状态不改变。
    调用可能抛出异常的操作,如果调用失败应该恢复到调用之前的状态,调用成功就是完成成功

  3. 不抛出异常保证: 承诺绝不抛出异常, 因为他们总是能完成它们原先承诺的功能。
    (作用于内置类型) nothrow

一: 使用 copy and swap 实现强烈保证级别的异常安全性。

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第64张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第65张图片

条款30: 透彻了解 inlining 的里里外外

  1. inlining 在C++程序中是编译期行为

  2. 过度热衷inlining会造成程序体积体大,即使有虚拟内存,inline 造成
    的代码膨胀亦会导致额外的换页行为,降低指令高速缓存装置的击中率,降低效率。

  3. virtual 的调用会使得 inlining 落空 (运行期确定的多态行为,当然会使得inlining落空)

  4. 大部分调试器面对 inline 函数都束手无策(不存在的函数设置断点 真的很荒唐)

总结:

  1. 将大多数 inlining 限制在小型,被频繁调用的函数身上,可以使得日后的调试
    和二进制升级更容易,也可使得潜在的代码膨胀问题最小化,使程序的速度提升机会最大化

条款31: 将文件间的编译依存关系降至最低

一: 相依赖于声明式,而不要相依定义式实现一个类,遵循以下

  1. 如果能够使用 object references 或者 object pointers可以完成任务,就不要使用 objects
    使用 objects 就需要定义该类型的定义式,在声明一个类时需要知道其sizeof大小
  2. 如果能够,尽量以 class 声明式替换 class 定义式

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第66张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第67张图片

总结:

  1. 支持“编译依存性最小化”的一般构想是: 相依于声明式, 不要相依定义式
    基于此构想两个手段是 Handle classes(将自身的实现作为另一个类,且将该类的指针作为自己的实现) 和 Interface classes(以抽象类作为接口)
  2. 程序库头文件应该以 “完全且仅有声明式” 的形式存在。

六. 继承与面向对象设计

条款32: 确定你的 public 继承素模出 is-a 关系

C++进行面向对象编程最重要的规则是:public inheritance 意味着是 is-a 的关系(正向一类)

一:验证 ia-a: D 继承 B,则每一个类型为D的对象本身也是类型为B的对象,反则不成立

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第68张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第69张图片

虽然正方形继承自长方形,但适用于长方形的行为并不适合正方形, 这显然是非常不正确的,
继承后遵循 is-a 的行为,因此正方形必须能适应长方形的所有行为。

总结:

public 继承意味着 is-a,适用于 base class 的事情也一定适用于 derived class身上
未遵循is-a的代码即使编译通过,但不保证程序的行为是正确的

条款33:避免遮掩继承而来的名称

一:derived class 内的名称会遮盖 base classes 内的名称, 在public 继承下从来没有人希望如此

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第70张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第71张图片
总结

  1. derived class 内的名称会遮盖 base classes 内的名称, 在public 继承下从来没有人希望如此
  2. 为了让遮盖的名称再见天日,可使用using声明式子,或转交函数 类名::function

条款34: 区分接口继承和实现继承

public 继承观念由两部分组成: 函数接口继承和函数实现继承

一: 浏览接口继承与实现继承的具体体现

pure virtual函数的目的是为了让 derived class 只继承函数接口
impure virtual 函数的目的是让 derived class 继承该函数的接口和缺省实现
non-virtual函数的目的是为了令derived class 继承函数的接口及一份强制性实现(不变性,不应该被重新定义)
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第72张图片

二: pure virtual 的定义 与 派生重写

我们可以为 pure virtual 提供一个定义,C++并不会发出怨言,但调用其途径仅 “调用时指定其class名称”
在这里插入图片描述

重要说明:pure virtual 函数必须在 derived class 重新声明,但也可以有自己的一份实现,需要显式调用而已, 适用于 接口与缺省情况并存的情况下 (比 impure class 好用)

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第73张图片
在这里插入图片描述

总结:

  1. 接口继承和实现继承不同,在public 继承下, derived classes总是继承 base class 的接口
  2. pure class 函数只具体指定接口继承
  3. 简朴的 impure virtual 函数具体指定接口继承与缺省实现继承
  4. non-virtual 函数具体指定接口继承以及强制性实现继承
  5. 一个典型的程序有 80% 的时间花费在 20% 的代码上,请将心里放在那举足轻重的代码上。

条款35: 考虑 virtual 函数以外的其他选择

  1. virtual 函数带来的虚函数表以及虚表指针的负担
  2. 其灵活性十分差,继承重写虚函数后不具有可变与可增内容灵活度

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第74张图片

virtual 函数以外的其他选择:

一: 使用 NVI(Non-Virtual Interface) 手法实现 Template Method 模式(主张 virtual应该总是private)

私有impure virtual 使得派生类重新定义, member function 调用其 impure virtual 实现(且在调用前后都可以做额外的事情)
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第75张图片

二: 以策略模式替换 virtual, 以基于对象的思路(std::bind + std::function)替换virtual+多态

策略模式的构成:

  1. GameCharacter 作为策略的执行者
  2. HealthCalcFunc 作为策略(角色生命值健康情况的计算)

演示1: 不使用bind+function,使用指针作为回调函数实现策略模式

Strategy 提供了有趣的弹性:

  • 同一人物类型之不同实体可以有不同的健康计算函数。
  • 某已知人物之健康指数计算函数可以在运行期变更。

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第76张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第77张图片

演示2: 由 std::function+std::bind 完成 Strategy 模式

std::function 这样的对象可保持任何可调用物质函数指针,函数对象,成员函数指针(而非仅函数指针)且具有一定的兼容性,可调用物的参数可以隐式转换为其function声明的参数以及返回值能隐式转换为 function声明的返回值 就可以兼容

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第78张图片

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第79张图片
总结:

  1. virtual 的替换手法八廓 NVI 手法以及 Strategy 设计模式的多种形式
    NVI 手法自身是一个特殊形式的 Template Method 设计模式
  2. 将机能从成员函数移到 class 外部函数,带来的一个缺点是,非成员函数无法访问
    class 的 non-public 成员。
  3. std::function 对象的行为就像一般函数指针,这样的对象可接纳 “与给定之目标签名式” 兼容
    的所有可用调用物。

条款36: 绝不重新定义继承而来的 non-virtual 函数

绝不重新定义继承而来的 non-virtual 函数

条款37: 绝不重新定义继承而来的缺省参数值

由于派生类永远不会重新定义继承来的 non-virtual 函数,所以我们这条是针对 virtual 而言的

一: 缺省参数值执行的是静态绑定, 而不是运行期再次确定,缺省参数遵循调用者的静态类型而定
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第80张图片
在这里插入图片描述
shape_sptr 的静态类型是 shape 的智能指针, 因此缺省值是 shape的draw的缺省值,而非 rectangle 的draw的缺省值,C++ 坚持以这种夸张的方式来运作是基于运行期效率来做的。

解决: 使用 NVI手法替换虚函数所表现的行为不是很满意的情况

base 的 public non-virtual 函数调用 private virtual, virtual 可被重新定义
我们让 non-virtual 函数指定缺省参数, private virtual 负责真正的工作

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第81张图片

总结:

  1. 绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数都是静态绑定,
    而virtual函数—你唯一应该覆写的东西-却是动态绑定。

条款38: 通过复合塑模出 has-a 或 “根据某物实现出”

复合包括

关联: 彼此并不负责对方的生命周期一般使用指针或者引用
聚合: 对象之间的关系表现为分为整体和局部, 整体部分并不负责局部对象的销毁
组合 : 对象之间的关系表现为分为整体和局部, 整体部分负责局部对象的销毁

is-a : 是继承,意味着 派生类必须能作为基类 完成基类能完成的所有功能
has-a: 是复合 挑选合适的关系帮我们完成更好的面向对象设计

一: 做一个必须以复合完成的功能实例, 以 list 实现 set

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第82张图片
总结:

  1. 复合的意义和public继承完全不同
  2. 在应用领域,复合意味着 has-a(有一个),在实现领域,复合意味着(根据某物实现出)

条款39: 明智而审慎地使用 private 继承

一: 理解private 继承所能实现的效果

  1. 将 base class 中的所有成员变为 private 属性
  2. 编译器不会自动将一个derived class 对象转换为一个 base class 对象
  3. private 继承意味着 根据某物实现出,而非 is-a 语义,只有实现部分被继承,接口不会被继承

编译器不会自动将一个derived class 对象转换为一个 base class 对象EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第83张图片
private 继承意味着 根据某物实现出,而非 is-a 语义,只有实现部分被继承,接口不会被继承EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第84张图片

二: 其实使用 public 继承加复合 比 private继承 更好

  1. 阻止了派生类重新定义virtual的要求
  2. 将Widget的编译依存性降至最低

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第85张图片

private 继承主要用于 “当一个意欲” 成为 derived class 者想访问一个
意欲成为 base class 者的protected成分,或为了重新定义一或多个 virtual 函数

空间最优化会促使你选择 private 继承而非 “继承加复合”
空类的sizeof为1,c++会为其安插一个char到空对象中,使得空对象间有一定的区分
EBO:空间基类最优化,将空类(未含成员的类)空间在继承后优化掉

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第86张图片

考虑过所有方案后,仍认为 private 继承是“表现程序内两个 classes 之间的关系”的最佳办法,才使用

总结:

  1. private 继承意味着 (根据某物实现出)的语义, 比复合的级别低,但是当
    derived class 需要访问 protected base class 的成员,或者重新定义继承而来的 virtual 函数时,是合理的
  2. 和复用不同,private继承可用造成 empty base 最优化, 这对致力于 “对象尺寸最小化” 的程序开发者来说很重要。

条款40: 明智而审慎地使用多重继承

一: 多个base class 的成员名字相同造成的歧义

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第87张图片
二. 菱形继承的问题(虚拟继承)

虚拟继承带来的影响

  1. 使用virtual继承的那些 classes 所产生的对象往往比使用 non-virtual
    继承的兄弟们体积大(安插共享指针),访问 virtual base classes 成员也慢

  2. virtual base 的初始化责任是由继承体系中的最底层(最高级别的派生类)
    负责。

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第88张图片
关于虚拟继承的建议

  1. 第一,非必要不使用 virtual bases,平常请使用non-virtual继承
  2. 第二,如果你必须使用 virtual base classes,尽可能避免在其中放置数据,这样
    可用避免在 base classes 初始化发生错误

三: 正确使用多重继承

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第89张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第90张图片
使用 CPerson 表现人的实体,公有继承接口(IPerson)与 私有继承实现 (PersonInfo)
由于CPerson是转调Personinfo的实现来完成自己的接口,且需要重新定义 virtual 函数
那么就具备 (根据某物实现的语义), 使用private继承或者 public继承+复合 来完成

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第91张图片

总结:

  1. 多重继承比单一继承复杂,且容易引起歧义,使用显示指定base class可解决
  2. virtual继承会增加大小,速度,初始化复杂度等成本,如果 virtual base classes
    不带任何数据,那将是最具实用价值的情况
  3. 多重继承的确有正当用途,其中最常用即public 继承某个 interface 与 private 继承某个协助实现的 class 的情况

七. 模板与泛型编程

条款41:了解隐式接口和编译期多态

一对template参数而言,接口是隐式的,基于有效表达式,多态是通过template具现化和函数重载解析发生于编译器。

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第92张图片

模板在实例化时会进行带入,之后发生编译器多态,对有效表达式进行检测
有效表达式即隐式接口(并非知道该隐式接口是否是有效,仅在具现化模板时进行编译期检测)
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第93张图片

总结:

  1. classes 和 templates 都支持接口和多态
  2. class 而言接口都是显示的,以函数签名为中心,多态则是通过virtual函数发生在运行期
  3. 对template参数而言,接口是隐式的,基于有效表达式,多态是通过template具现化和函数重载解析
    发生于编译器。

条款42:了解 typename 的双重意义

一. typename 定义模板类型

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第94张图片
二: 从属名称与 typename 指涉嵌套从属类型名称

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第95张图片

嵌套从属类型名称不需指定 typename 的情况:

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第96张图片
对内嵌从属类型进行 起别名:

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第97张图片

总结:

  1. 声明template参数时, 前缀关键字 class 与 typename 可互换
  2. 请使用关键字 typename 标识嵌套从属类型名称, 但不得在 base class lists(基类列)
    或 member initialization list(成员初始值列)内以它作为 base class 修饰符。

条款43: 学习处理模板化基类内的名称

一: 善用模板特例化解决特殊情况

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第98张图片

这里: 如果 Company 没有 sendClearText,就会使得调用失败

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第99张图片

使用模板特例化完成针对 CompanyZ 的MsgSender(使Company具现化在定义时)

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第100张图片

二: C++ 模板继承时由于 base class 并没有具现化,C++并不知道继承的究竟是什么类,因此其内含的 members全部被隐藏, 因此继承而来的 sendClearMsg 会被隐藏

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第101张图片

总结

  1. 对于特别的模板参数类型可采用模板特例化完成
  2. 可在派生template类内通过 this-> 或 using 暴露其 base class template 成员名称

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

一. 非类型模板参数会带来代码膨胀

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第102张图片

二: 共性与变性的分析, 对类以 private 继承 或 复合 来抽离代码,并以函数参数或class成员变量替换 template 参数

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第103张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第104张图片
在这里插入图片描述

三. 类型模板参数也会导致膨胀,比如 vector 与 vector, 但只要二进制表述相同(参考指针的空间字节数)我们可以实现共享码

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第105张图片

总结

  1. Templates 生成多个classes 和 多个函数,所以任何 template 代码都不应该与 某个造成膨胀
    的 template 参数形成相依关系

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

  3. 因类型参数而造成的代码膨胀,往往可降低,做法是以相同二进制表述的具现类型共享实现码。

条款45: 运用成员函数模板接受所有兼容类型

一:同一个template的不同具现体之间不存在固有关系,需要定义泛化的隐式转换

具现体的基本类型存有 转换(派生类指针到基类指针的转换),但具现体并不具备
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第106张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第107张图片

解决: 写出一个泛化copy构造函数来兼容,限制工作交给实际的类型去转换

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第108张图片
在这里插入图片描述

二: 泛化的 copy 构造函数并不会阻止编译器生成它们自己的 copy 构造函数, 如果你想完完全全控制 copy动作,请写出泛化版本与非泛化版本

总结

  1. 请使用 member function templates(成员函数模板)生成 ”可接受所有兼容类型“ 的函数
  2. 泛化的 copy 构造函数并不会阻止编译器生成它们自己的 copy 构造函数,
    如果你想完完全全控制 copy动作,请写出泛化版本与非泛化版本

条款46: 需要类型转换时请为模板定义非成员函数

一. 复现 24 (”唯有non-member函数才有能力在所有实参上实施隐式类型转换“)在template不适用

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第109张图片

原因: template 在实参推导过程中并不考虑”通过构造函数而发生的隐式类型转换“,因此 operator*在该情况下并不会被推导出

二. 解决: 以template 相关的 ”函数支持所有参数之隐式类型转换“时,请将那些函数定义为 class template 内部的friend函数

在template class 中指涉 operator(**)函数为友元,由于类模板推导不依赖 tempalte 实参推导(施行于 function templates上),所以编译器总是能够在class Rational 具现后找到 友元函数 operator*的声明 (并因此缓和 template 实参推导)

注意: friend 仅代表声明, 我们需要定义,可以在友元声明处直接定义,或定义额外的non-member供友元调用

这里解释一下

  1. 为了让类型转换可能发生在所有实参上,我们需要一个 non-member 函数,为了让
    这个函数被自动具现化, 我们需要将它声明在 class 内部,而在 class 内部声明 non-member 的唯一方法是友元。

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第110张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第111张图片

总结:

  1. 当我们编写一个 class Template, 而它所提供之 ”与此template相关的“ 函数支持 ”所有参数之隐式类型转换“ 时,请将那些函数定义为 ”class template“ 内部的friend 函数。

条款47: 请使用 traits classes 表现类型信息

一: 迭代器类型的区分与traits(类型萃取技术)的实现

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第112张图片
STL 中 通过萃取容器得到其 iterator 类型之后才能实施不同的算法
萃取技术的实现:

  1. 确定若干你想将来可取得的类型相关信息
  2. 为该信息选择一个名称
  3. 提供一个 template 和一组 特化版本(有需要的话),内含你希望支持的类型信息

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第113张图片
使用:
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第114张图片

二: 模仿 STL 中的做法,使用重载解决 CharacterTraits::category 在编译阶段能完成
但由于if 的原因却是推迟到运行期核定的问题 得到解决

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第115张图片

三: 如何正确使用一个 traits class

  1. 建立一组重载函数(劳工)或函数模板,彼此之间的差异仅在各自的 traits 参数,令每个函数实现码与 其接收之 traits 信息相应和

  2. 建立一个控制函数(身份像工头)或函数模板(advance),它调用上述那些 ”劳工函数“并传递 traits class 所需信息

总结:

  1. Traits classes 使得 ”类型相关信息“ 在编译期可用, 他们以 templates 和 ”templates特化“ 完成实现
  2. 整合重载技术后, traits classes 有可能在编译期对类型执行 if…else 测试

条款48: 认识 template 元编程

一: 了解什么是 template 元编程(TMP template metaprograming)

简介: TMP 是编写 模板程序 并执行于编译期的过程(也可以是说执行于C++编译器内的程序)。
优点:执行与编译期,检测错误更早,程序更高效,较小的执行文件,较短的运行期,较少的内存需求。
缺点:编译时间变长。

二: TMP 实现计算阶乘

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第116张图片

总结:

  1. TMP 可将工作由运行期移到编译期,因而得以实现早期错误侦测和更高的执行效率
  2. TMP 被用来 生成 ”基于政策选择组合“ 的客户定制代码,也可以避免生成对某些特殊类型
    并不适合的代码:

八. 定制 new 与 delete

条款49: 了解 new-handler 的行为

一: 认识 new-handler 和 set_new_handler,并懂得设计一个 new-handler

new-handler: 当operator new 抛出异常以反映一个未获满足的内存需求之前,它会现调用一个客户指定的错误处理函数,一个所谓的 new-handler

set_new_handler: 参数是指针,用于传入指定的 new-handler函数,返回值是( 被替换的那个 new-handler)

std下的标准库函数声明,模拟
在这里插入图片描述
使用 set_new_handler
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第117张图片

二: 设计一个良好的 new-handler

当operator new无法满足内存申请时,它会不断调用 new-handler 函数,直到找到足够内存
那就是一个设计良好的 new-handler 函数必须做以下事情:

  1. 让更多内存被使用: 如果operator new 失败,下一次的分配动作可能成功
    策略: 程序一开始就分配一大块内存,而后当 new-handler 第一次被调用,将它们释放给程序使用

  2. 安装另一个new-handler, 如果目前这个new-handler 并不能满足,让它有能力知道另外的 new-handler可以分配.
    策略: 令new-handler 修改 “会影响 new-handler行为” 的static数据,namespace数据或 global 数据

  3. 卸除 new-handler: 将 null 指针传给 set_new_handler ,一旦没有安装任何任何的 new-handler, operator new 会在内存分配不成功时抛出异常

  4. 抛出 bad_alloc(或派生自 bad_alloc) 的异常, 这样的异常不会被operator new 捕捉,因此 会被传播到内存索求处。

  5. 不返回,通常调用abort 或 exit

三. 根据每个 class 不同定制不同的内存分配失败情况

1. 首先需要 class 提供自己的 set_new_handler 和 operator new

set_new_handler 使得客户得以指定 class 专属的 new-handler
operator new 确保在分配 class 对象内存的过程中以 class 专属之 new-handler 替换 global new-handler

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第118张图片

set_new_handler 任务

  1. 设置新 new-handler
  2. 返回旧 old-handler

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第119张图片

operator new 任务

1.(更换错误处理函数) 调用标准 set_new_handler 设置自身保存的 current_handler,
2. (执行内存分配) 分配失败则 global operator new 会调用 current_handler
3. 其中 new-handler 在以异常的方式处理或退出operator new时,应注意将 class 中的 current_handler 回复到第1步设置之前
(保证不影响接下来的内存分配动作)

注意: 第3点使用RAII处理更好

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第120张图片

使得更通用: 由于该动作并不因 class 的不同而不同, 将 Widget 的 operator new 与 set_new_handler 动作进行复用是必要的

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第121张图片
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第122张图片

三. nothrow 形式
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第123张图片

总结:

  1. set_new_handler 允许客户指定一个函数,在内存分配无法获得满足条件时被调用
  2. Nothrow new 是一个颇为局限的工具,因为它只适用于内存分配,后继的构造函数调用
    还是可能抛出异常。

条款50: 了解 new 和 delete 的合理替换时机

总结:

  1. 为了检测运用错误
  2. 为了收集动态分配内存之使用统计信息
  3. 为了强化效能
  4. 为了增加分配和归还的速度
  5. 为了降低缺省内存管理器带来的空间额外开销
  6. 为了弥补缺省分配器中的非最佳齐位
  7. 为了将相关对象成簇集中 (减少内存页错误的出现频率)
  8. 为了获得非传统的行为

条款51: 编写 new 和 delete 时需固守常规

一: 编写 operator new 需注意的规矩

  1. 内含一个无限循环, 并在其中尝试分配内存,无法满足时调用 new-handler
  2. 处理 0 bytes 申请
  3. Class专属版本应该处理 ”比正确大小的(错误)“ 申请

operator new(伪码)
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第124张图片

基本实现版本:
EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第125张图片
二. 编写 operator delete 需注意的规矩

  1. C++保证删除 null 指针永远安全
  2. Class专属版本应该处理 ”比正确大小的(错误)“ 申请

伪码:

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第126张图片
基本实现:

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第127张图片

总结:

  1. operator new 应该内含一个无穷循环, 并在其中尝试分配内存,如果它无法满足内存需要
    就应该调用 new-handler,它也应该有能力处理 0 bytes申请, Class 专属版本则还应该处理
    ”比正确大小更大的(错误)申请“
  2. operator delete应该在收到 null 指针时不做任何事, Class 专属版本则还应该处理
    ”比正确大小更大的“ (错误) 申请。

条款52: 写了 placement new 也要写 placement delete

一: 关于使用 new 因构造函数抛出异常出现的内存泄漏问题

placement new 要与 placement delete 对应 才能使得运行期系统寻找到 处理异常导致内存泄漏问题。

问题:
new 一个对象有两部,一部是分配内存,一步调用对象的构造函数

  • 如果内存分配成功,但构造函数抛出异常,内存会交付给运行期系统处理,但需要对应的 delete 版本
  • 如果使用了 placement new ,但未定义对应的 placement delete 就会出现问题

解决: 定义参数个数与类型相同的 placement new 与 placement delete

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第128张图片

二. 解决派生类隐藏 Base 的 placement new 与 placement delete版本

问题:Base 声明的 placement new 与 placement delete 会被 Derived 定义的隐藏掉
标准形式的 operator new 与 operator new 也会因为 Class 声明而被隐藏

解决: 将正常形式的 new和delete 全部放在 一个 Base class里
Derived class 声明 placement new 与 delete, 且使用using声明式子将 Base class 中的
operator new 和 delete 暴露

EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)_第129张图片

总结:

  1. 当你写一个 placement operator new, 请确定也写出了对应的 placement
    operator delete, 如果没有这样做,你的程序可能会发生隐微而时断时续的内存泄漏

  2. 当你声明了 placement new 与 delete, 请确定别无意识第遮掩了 他们的正常版本

九. 杂项讨论

条款53: 不要忽视编译器的警告

条款54:让自己熟悉 TR1 在内的标准程序库

条款55: 让自己熟悉 Boost



你可能感兴趣的:(走进,C/C++后台开发,c++,github,c语言,c#,java)