目录
- 1 条款 01:视 C++ 为一个语言联邦
- 2 条款 02:尽量以 const、enum、inline 替换 #define
- 3 条款 03:尽可能使用 const
- 4 条款 04:确定对象被使用前已先被初始化
- 5 条款 05:了解 C++ 默默编写并调用那些函数
- 6 条款 06:若不想使用编译器自动生成的函数,就该明确的拒绝他们
- 7 条款 07:为多态基类声明 virtual 析构函数
- 8 条款 08:不要让异常逃离析构函数
- 9 条款 09:绝不再构造和析构过程中调用 virtual 函数
- 10 条款 10:令 operator = 返回一个 *this 的引用
- 11 条款 11:在 operator = 中处理 “自我赋值”
- 12 条款 12:复制对象时勿忘其每一个成分
- 13 条款 13:以对象管理资源
- 14 条款 14:在资源管理类中小心 copying 行为
- 15 条款 15:在资源管理类中提供对原始数据的访问
- 16 条款 16:成对使用 new 和 delete 时要采取同样的形式
- 17 条款 17:以独立的语句将 newed 对象植入智能指针
- 18 条款 18:让接口容易被正确使用,不易被误用
- 19 条款 19:设计 class 犹如设计 type
- 20 条款 20:宁以 const 引用传参替换以值传递
- 21 条款 21:必须返回对象时,别妄想返回其引用
- 22 条款 23:宁以 非成员 或 非友元 函数替换 成员函数
- 23 条款 24:若所有参数皆需要类型转换,请为此采用 非成员 函数
- 24 条款 25:考虑写出一个不抛出异常的 swap 函数
- 25 条款 26:应尽可能延后变量定义式的出现时间
- 26 条款 27:尽量少做转型动作
- 27 条款 28:避免返回 handles 指向对象内部成分
- 28 条款 29:为 “异常安全” 而努力是值得的
- 29 条款 30:透彻了解 inlining 的里里外外
- 30 条款 31:将文件间的编译依存关系降到最低
- 31 条款 32:确定你的 public 继承塑造出 is-a 关系
- 32 条款 33:避免遮掩继承而来的名称
- 33 条款 34:区分接口继承和实现继承
- 34 条款 35:考虑 virtual 函数以外的其他选择
- 35 条款 36:绝不重新定义继承而来的非虚函数
- 36 条款 37:绝不重新定义继承而来的缺省参数值
- 37 条款 38:通过复合塑造出 has-a 或 “根据某物实现出”
- 38 条款 39:明智而审慎地使用 private 继承
- 39 条款 40:明智而审慎地使用多重继承
- 40 条款 41:了解隐式接口和编译期多态
- 41 条款 42:了解 typename 的双重意义
- 42 条款 43:学习处理模板化基类内的名称
- 43 条款 44:将与参数无关的代码抽离 templates
- 44 条款 45:运用成员函数模板接收所有兼容类型
- 45 条款 46: 需要类型转换时请为模板定义非成员函数
- 46 条款 47:请使用 traits classes 表现类型信息
- 47 条款 48:认识模板元编程
- 48 条款 49:了解 new-handler 的行为
- 49 条款 50:了解 new 和 delete 的合理替换时机
- 50 条款 51:编写 new 和 delete 时需固守常规
- 51 条款 52:写了 placement new 也要写 placement delete
- 52 条款 53:不要轻视编译期警告
- 53 条款 54:让自己熟悉包括 TR1 在内的标准程序库
- 54 条款 55:让自己熟悉 Boost
条款 01:视 C++ 为一个语言联邦
C++分为:C部分、对象C++部分、模板C++部分、STL部分。
条款 02:尽量以 const、enum、inline 替换 #define
const: 表示修饰的内容不可更改。
enum: 本质是 int 类型。
inline: 在 class 内时,如果函数允许,他被自动启用。
条款 03:尽可能使用 const
这有利于编译器更好的优化程序,并且让客户减少误操作。
在函数传值时,对于自定义类型,最好使用 const 引用。
用 mutable 修饰成员变量,表示允许成员 const 方法改变该变量。
为避免代码重复,请用非 const 函数调用 const 函数,而不是颠倒。
条款 04:确定对象被使用前已先被初始化
在 class 的构造函数表中显式写出所有成员函数,且按照声明次序。
成员变量的初始化次序与声明次序相同。
如果有变量被用于声明全局资源:
class FileSystem {
// TODO: ...
};
extern FileSystem tfs;
为了保证构造次序正确,请放到工厂函数中:
FileSystem & tfs() {
static FileSystem fs;
return fs;
}
条款 05:了解 C++ 默默编写并调用那些函数
class Empty {
pubilc:
Empty() { ... }
Empty(const Empty & rhs) { ... }
Empty(const Empty && rhs) { ... }
~Empty() { ... }
Empty & operator = (const Empty & rhs) { ... }
Empty & operator = (const Empty && rhs) { ... }
};
以上是全部的默认函数,最好全部自己编写他们。
条款 06:若不想使用编译器自动生成的函数,就该明确的拒绝他们
如题,在函数后面使用 = delete 删除默认函数。
条款 07:为多态基类声明 virtual 析构函数
使用指向具有非 virtual 析构函数 的基类的指针来删除派生类对象会导致未定义的行为。为了纠正这种情况,应该用 virtual 析构函数来定义基类。
使基类析构函数 virtual 保证派生类的对象被正确地析构。
任何时候在类中都有一个 virtual 函数,您应该立即添加一个 virtual 析构函数。
条款 08:不要让异常逃离析构函数
解构函数不可抛出异常,如果一定有异常,就要直接结束程序。
或者将其异常部分放在另一个函数中,允许用户提前以其他方式调用。
条款 09:绝不再构造和析构过程中调用 virtual 函数
条款 10:令 operator = 返回一个 *this 的引用
条款 11:在 operator = 中处理 “自我赋值”
首先为 rhs 数据创建副本,然后交换副本。
条款 12:复制对象时勿忘其每一个成分
在 copy 函数中调用基类的 copy 函数,构造 copy 时。
不要因两个 copy 函数中有重复代码就让其中一个调用另一个,应放在第三个函数中。
条款 13:以对象管理资源
资源指类似 GLFW 的窗口一样的东西,在 create 后,需要用特定函数 delete 。
如果不是用对象管理,你的客户可能会忘掉 delete 。
std::auto_ptr:智能指针,其复制函数会把复制方置为 nullptr 。
不允许多个对象持有同一个指针。
std::tr1::shared_ptr:同上,不过允许一个指针被多个对象持有。
他在最后一个对象销毁时销毁指针,允许指定特定的 delete 函数。
条款 14:在资源管理类中小心 copying 行为
这可能导致资源在使用时已经被销毁。
条款 15:在资源管理类中提供对原始数据的访问
返回原始数据的指针。
条款 16:成对使用 new 和 delete 时要采取同样的形式
条款 17:以独立的语句将 newed 对象植入智能指针
防止出现异常时,出现内存泄漏。
条款 18:让接口容易被正确使用,不易被误用
让 class 的行为尽量与 int 和 STL 相似。
条款 19:设计 class 犹如设计 type
- 新 type 的对象如何创建与销毁
- 对象的初始化和赋值有什么区别
- type 如果被以值传递会发生什么
- 什么是 type 的合法值
- 需要配合某个继承图系吗
- type 需要什么样的转换
- 应该有哪些操作符和函数合法
- 哪些编译器自动生成的函数需要驳回
- 谁该是新 type 的成员
- 什么是新 type 的 “未声明接口”
- 需要模板化吗
条款 20:宁以 const 引用传参替换以值传递
条款 21:必须返回对象时,别妄想返回其引用
直接在 return 语句中使用构造,创建一个新的对象。
条款 23:宁以 非成员 或 非友元 函数替换 成员函数
条款 24:若所有参数皆需要类型转换,请为此采用 非成员 函数
使用成员函数重载 operator 时,如果 的左端可能需要转换,请使用非成员函数。
条款 25:考虑写出一个不抛出异常的 swap 函数
例如在 pimpl 手法中只需要交换指针。
最好将 std::swap 偏特化,便于客户使用。
如果无法偏特化(比如模板 class),也不要考虑为 std 加入重载函数。
在 class 的命名空间中放置特殊的 swap 函数是不错的。
条款 26:应尽可能延后变量定义式的出现时间
条款 27:尽量少做转型动作
C 转型为 类型名+变量名 例如:(int)x 或 int(x) 。
C++ 提供的新的转型方案:
const_cast:可以移除常量性。
dynamic_cast:确保安全的先下转型。(可能带来更多资源消耗)
reinterpret_cast:低级转型,比如把 int指针 转换为 int 。
static_cast:强迫隐式转型,和 C 转型大致相同。
少用 C 转型!
条款 28:避免返回 handles 指向对象内部成分
handles:像是非 const 引用和指针一样可以改变内部数据的东西。
条款 29:为 “异常安全” 而努力是值得的
基本承诺:异常抛出后,程序内的任何事物依然保持有效状态。
但是无法确定有效状态是函数执行前的状态,还是恢复到默认缺省状态。
强烈保证:如果函数失败,程序回到函数调用前。
无异常保证:函数不会失败,用 noexcept 修饰。或是失败对程序是致命的。
强烈保证的实现方法可以是,先处理一个副本,然后与原始数据 swap 。
条款 30:透彻了解 inlining 的里里外外
在 class 声明中定义的函数,常常是 inline 的。
在 class 中的空函数不一定真的为空,这时不要使用 inline 。
(他可能调用了父类及成员的函数)
条款 31:将文件间的编译依存关系降到最低
尽量使用前置 class 声明。
这会导致成员变量问题,可以用 pimpl idiom 解决。
如果可能,尽量用引用和指针代替对象本体。
如果可能,尽量用 class 声明代替 class 定义。
为声明式和定义式创建不同的头文件。(以 fwd 作为头文件后缀)
条款 32:确定你的 public 继承塑造出 is-a 关系
即每一个子类也是基类。(每一个学生(子类)是人(基类))
你需要想想,正方形是矩形吗?矩形改变高度的属性继承给正方形会发生什么?
条款 33:避免遮掩继承而来的名称
遮掩就是一个作用域内的变量可以屏蔽同名全局(外作用域)的变量。
子类的函数会遮掩父类的函数。
如果想继承父类函数,仅仅只是想重载,请在子类中使用 using 引用。
条款 34:区分接口继承和实现继承
成员函数接口一定被继承,父类可做的事,子类也必须可做。
可以为纯虚函数建立定义,但在调用时需要明确标出作用域名。
纯虚函数继承定义。
虚函数继承定义和缺省实现。
非虚函数继承定义和强制性实现。
条款 35:考虑 virtual 函数以外的其他选择
Non-Virtual Interface 的 Template Method 模式:让一个非虚成员函数调用虚的内部虚函数,方法在调用虚函数前进行事前处理,比如互斥锁等等。
Function Pointer 的 Strategy 模式:类似 GLFW 的回调函数,采用绑定,在函数构造中绑定函数。
tr1::function 的 Strategy 模式:tr1::function 可以自动兼容函数指针。
传统的 Strategy 模式:将虚函数放入另一个继承体系。
条款 36:绝不重新定义继承而来的非虚函数
条款 37:绝不重新定义继承而来的缺省参数值
但是缺省参数值是静态绑定,所以最好让静态函数调用内部虚函数,实现动态绑定。
条款 38:通过复合塑造出 has-a 或 “根据某物实现出”
复合:在 class 中放置一个 class 。
成员与 class 的关系通常是 has-a (有一个)的。
根据某物实现出通常用于使用另一个 class 的代码,但不是 is-a 关系。
条款 39:明智而审慎地使用 private 继承
private 继承不存在继承关系,即不是 is-a 关系。
他是 根据某物实现出 关系的。
private 继承意味着父类中的 public 成员将变成 private 。
通常用于接口。private 继承可以被复合代替。
EBO:一个空 class 如果当做成员也占用空间,但是继承不会。
(空类不是真的空,他通常拥有静态对象)
条款 40:明智而审慎地使用多重继承
如何多重继承中有基类成员重名,需要明写所在空间。
virtual public:虚继承,会降低程序效率,但可避免继承一个 class 多次。
多重继承常用于类似 java 的接口的功能。
条款 41:了解隐式接口和编译期多态
不同的模板在编译期被确定调用那些函数。
class 的接口是显示的,可以在定义式中看到,模板则不是。
条款 42:了解 typename 的双重意义
template<class T> class Widget;
template<typename T> class Widget;
以上两个式子并没有去别,但最好使用 class 。
typename C::const_iterator it;
typename 可以用来确定其后面的表达式是一个类型,而不是函数等等。
typename 必须做为嵌套从属类型的前缀,不能在基类表列和初始化表列存在。
条款 43:学习处理模板化基类内的名称
因为模板基类可以被特例化,所以即使继承了基类也无法使用其成员。
但是可以通过定义资格修饰符来假设他有。或使用 this 指针。
条款 44:将与参数无关的代码抽离 templates
使用模板可能导致二进制码膨胀。
以 class 成员代替模板参数消除膨胀。
条款 45:运用成员函数模板接收所有兼容类型
为模板 class 制作一个泛化 copy 函数。
即使你制作一个了泛化 copy 函数,但是你的程序还是缺省 copy 函数。
条款 46: 需要类型转换时请为模板定义非成员函数
以 class 模板内部的友元函数定义。
条款 47:请使用 traits classes 表现类型信息
这个技术用来确定类型是否合法(如是否是内置类型),在编译期给出警告。
迭代器:
input:只能向前移动,而且只读,读仅一次。
output:只能向前移动,而且只写,写仅一次。
forward:可向前向后移动,可读写,在一些实现中可能为单向。
bidirectional:可向前向后移动,可读写,例如 set 的。
random access:可向前向后任意长度,可读写。例如 vector 的。
traits classes:
重载函数:差别仅仅是 traits 参数,用来区分调用不同的函数。
控制函数:调用重载函数。
条款 48:认识模板元编程
模板元编程是在编译期执行的程序。
下面是 TMP 的一个例子,他可以在编译期获得阶乘:
template<unsigned int n>
struct Factorial {
enum { value = n * Factorial<n - 1>::value };
};
template<>
struct Factorial<0> {
enum { value = 1 };
};
条款 49:了解 new-handler 的行为
new-handler 是在 new 时发现空间不足时调用的提示函数。
他不接受任何参数也不返回任何参数。
可以使用 set_new_handler 函数来设置自定义函数,该函数返回上一个 new-handler 函数。
条款 50:了解 new 和 delete 的合理替换时机
- 用来检测运用上的错误。
- 为了收集使用时的统计数据。
- 增加运行速度。
- 减少空间使用。
- 弥补缺省的分配器的非最佳齐位。
- 让对象成簇放置。
- 为了获得非传统行为。
条款 51:编写 new 和 delete 时需固守常规
- 处理 0-byte 申请。(视其为 1-byte 申请)
- 确保不会出现内存泄漏。
- 为 class 特制时,如果申请大小不符,自动调用默认函数。
- new 中应该有一个 while(true)
- 妥善处理 nullptr
条款 52:写了 placement new 也要写 placement delete
这些函数用于在特定的位置申请内存。
请不要无意识的使其掩盖正常版本的分配器。
条款 53:不要轻视编译期警告
条款 54:让自己熟悉包括 TR1 在内的标准程序库
TR1 自身只是一份规范,主要实现来着 Boost 。
组件实例:
- 智能指针 – tr1::shared_ptr 和 tr1::weak_ptr:后者可以处理环。
- tr1::function:表示所有可调用体,类似函数指针。
- tr1::bind:STL绑定器。
独立机能部分:
- Hash tables:用来实现容器。
- 正则表达式。
- Tuples:变量组,类似 pair ,但可以持有更多变量。
- tr1::array:数组。
- tr1::mem_fn:成员函数指针。
- tr1::reference_wrapper:让引用的行为更像对象。
- 随机数。
- 数学库。
- C99扩充。
模板编程部分:
- Type traits:用于判断是否是合法类型。
- tr1::result_of:用来推导函数返回值类型。