01:视 C++为一个语言联邦¶
C、面向对象C++、模板C++、STL
02:尽量以 Const,enum,inline 替换#define¶
- 编译器替代预处理器
条款 04:尽可能让对象被使用前初始化¶
- 某些条件下不会赋值 0,而是随机数,这会导致错误
- 注意初始化和赋值的区别:初始化早于构造
- 初值列也会更高效一些
- 请尽量使用自定义构造初始化:通过default构造函数构造出一个对象对它赋值比直接构造时指定初值效率差。
05:了解 C++默默编写并调用哪些函数?¶
- 如果 empty class 没有声明,C++会默认声明default 构造、copy 构造、copy assignment 操作符、non-virtual的析构函数,并且他们都是public 且 inline 的。
- 只有被调用时,它们才会被创建出来
- 注意:编译器产生的析构函数是non-virtual 的
- 代码不合法,会拒绝生出operator=。比如类的成员是一个引用,而operator= 不可能拷贝引用。此外,const 的赋值也是一个典型的例子。c++会拒绝编译
06:若不想使用编译器自动生成的函数,就应该明确拒绝¶
- private拒绝,或者=delete
09:绝不在构造和析构中调用 virtual¶
- base 的构造函数调用期间,virtual 函数绝对不会下降到 derived class,会通向不明确的行为(UB?不对,实测调用的是本类的函数)。
- 析构也一样,derived class 的析构开始执行时,对象内的成员变量便呈现未定义值。进入 base class 后就会变成一个 base class 对象。
10:operator=
的连锁赋值实现return* this¶
- 并非强制
- 如何实现连锁赋值 x = y = z =15?
- 该规则同样适用于
*= += -=
等
11:在 operator= 中处理“自我赋值”¶
- 正常是 w=w
- 但有时候会有潜在的自我赋值比如
a[i] = a[j]
,*px = *py
,Base& rb,Derived* pd
- 添加自测(identity test),自我赋值时,不做任何事.(缺乏异常安全性,new 出问题了,可能导致返回一块被删除的类)
- 且测试需要成本(代码变大,新的控制流分支),需要平衡大小和效率。
Widget& Widget::operator= (const Widget& rhs) { if(this == &rhs) return *this;//证同测试 delete pb; pb = new Bitmap(*rhs.pb); return *this; }
- 且测试需要成本(代码变大,新的控制流分支),需要平衡大小和效率。
- 异常安全性的做法:
Widget& Widget::operator= (const Widget& rhs) { Bitmap* pOrig = rhs; pb = new Bitmap(*rhs.pb); delete pOrig; return *this; }
- pass by value 的一个高效代码(但不清晰)
Widget& Widget::operator= (Widget rhs) { swap(rhs); return *this; }
12:copying 复制对象勿忘其每一个成分¶
- 不要忘记复制base class的东西
资源管理¶
13.对象管理资源¶
请使用RAII,防止资源泄露 - 资源的RAII,锁的RAII都需要注意
14. RAII Copying:禁止copy或者引用计数¶
15.资源管理类RAII class应当提供对原始资源的访问¶
16. 成对使用new和delete,特别是[]
¶
17.(过时)以独立语句将newd对象置入智能指针,避免函数调用顺序影响结果¶
- 过时原因:C++17之前调用顺序不明确,C++17之后表达式求值明确了从左到右
- 注意区分入栈顺序:从右到左,确保首位在栈顶。
设计与声明¶
18.让接口容易被正确的使用。¶
- 典型:年月日,不要设计成3个int,而是封装成3个可从int转换的结构体
- 阻止误用包括:建立新类型,限制类型上的操作,束缚对象值,消除客户的资源管理责任。
- sharedptr可有效防范cross-dll-problem,以及自动解除互斥锁
20.尽量以常量引用代替值传参¶
21.⭐(被坑过)返回对象时,别妄想返回Reference¶
- local stack对象不行,因为原对象没了
- heap-allocated对象不行,因为不知谁给原对象进行delete
- static对象也不行,以为引用只是别名,调用函数得到的返回值引用最后一致。
其实指针也有坑¶
- local stack,原对象没了
- static对象不行,实际内存一致。
22.成员变量声明为private¶
23. 非成员、非友元函数比成员函数更具有封装性和扩充性。¶
- 以非成员函数(或者一些工具类的成员函数)包裹成员函数来提供接口,更具有封装性,以及包裹弹性(易修改)、机能扩充性。
24. 若所有参数皆需类型转换,请采用非成员函数。¶
25. swap特化:不抛异常的swap函数¶
- swap:异常安全的脊柱,处理自我赋值可能性的常见机制
- std的swap对自己的类型效率不高的话,可以提供一个成员函数swap
- 也请提供一个非成员函数swap调用成员swap。对于classes,请特化swap。
实现¶
26. 尽量延后变量定义式的出现¶
- 代码清晰,异常安全,改善程序效率
- 构造函数定义,代替常规赋值,效率更高。
27.尽量少做转型¶
- 有也请尽量用新型转型,有分门别类和编译器、运行期错误检查。
- 特别是注重效率的代码尽量避免dynamic_cast。
- 转型如果必要,试着将其隐藏于某个函数背后。客户调用不需要单独转型
28.尽量避免返回handles指向内部成员的部分¶
- 防止拿到对象内部成员引用,使得内部成员可写
- 如果不反悔函数的引用返回值,也可以尽量避免空悬指针问题的产生
29.为异常安全努力¶
- 异常抛出时,异常安全的函数的特点:
- 不泄露任何资源(资源释放、锁释放),RAII是个很好的解决办法
- 不允许数据败坏(异常问题导致资源损坏,但依然被引用),
- 异常安全函数三个保证,请尽量做到三种之一
- 基本承诺:异常抛出时,继续持有原因资源或者使用缺省资源
- 强烈保证:函数成功就是完全成功,函数失败恢复到调用前。常用copy and swap。但很多情况难以是实现
- nothrow保证:保证不会抛出异常,能正常完成功能
30.透彻了解inline¶
- 本质:函数本体替换函数调用,inline声明
- 如果过大,额外的效率损失:虚拟内存换页损失、降低缓存命中率。但是小于函数调用开销的码,则相反!
- 隐喻inline申请:定义在class内部
- inline和templates通常都定义于头文件:编译期行为,需要知道其真实是什么样子的。
- 所有使用inline的函数的模块都要重新编译!
31.文件间的编译依存将至最低¶
- (头文件)编译依存过大:重新Build/make一次,导致C++全部重新编译连接
- 前置声明:只声明不定义,注意细节:
- 标准头文件不建议前置声明,一般不会成为编译瓶颈,特别是预编译头存在时
- 编译器必须在编译期间知道对象的大小,那么就必须用指针或引用来获取前置声明的对象。否则没有class 定义是