条款04 确定对象使用前已被初始化
尽量使用成员初始化列表(member initialization list)而不是赋值初始化(assign initialization );C++ 类内成员变量初始化顺序和其被声明顺序相同,所以声明的时候要设计好顺序。
static对象: 其生命期为从被构造出来到程序结束。这种对象包括global对象,声明为static的对象。
C++无法确定 不同编译单元non-local static对象的初始化顺序,所以要使用的话最好使用一个reference-return函数(类似单例模式)。
条款05 了解C++默默编写并调用了哪些函数
如果类内含有不支持赋值操作的成员(声明为const的对象,reference等),编译器不会生成默认拷贝构造函数和默认赋值操作符。
还有一种情况是,如果基类将拷贝构造函数声明为private,那么编译器也不会为派生类生成默认拷贝构造函数。
条款06 若不想用编译器自动生成的函数,应该明确拒绝
例如生成一个不可拷贝的对象,可以将copy构造函数和赋值操作符声明为private
,光这样并不安全,因为友元可以访问到,所以这里有一个trick,就是只声明而不去定义,如果友元中试图去调用就会得到一个连接错误。
当然在C++11
里就很简单了,只需要将其声明为=delete
。
条款07 将多态基类的析构函数声明为virtual
如果一个类不是用作基类或者多态,不应该为其声明virtual析构函数。
virtual函数会使类的体积变大(因为虚函数表)。
条款08 别让异常逃离析构函数
析构函数绝对不要抛出异常,因为可能导致不确定的行为。
使用一种双保险的技巧,将可能导致异常的函数开放给用户,如果用户不去处理则在析构函数中try catch
异常。
条款09 不要在构造/析构函数中调用虚函数
在基类构造和析构函数中,派生类将不会被看作派生类,所有虚函数不会下降到派生类那一层。
条款11 在operator=中处理自我赋值
确保对象在自我赋值时的安全性
确保一个函数如果操作多个对象,这多个对象是同一个对象时的安全性。
条款12 copy对象时不要忘了每个成分
确保拷贝构造函数中,1.拷贝所有local变量,2.派生类要负责调用基类部分的拷贝函数。
切忌在拷贝构造函数中调用copy assignment操作符,反之也不要在拷贝赋值操作符中调用copy构造函数。
条款13 用对象管理资源
为了防止资源泄露,请使用RAII对象,在构造函数中获取资源,在析构函数中释放对象。
条款14 在资源管理类中小心copying行为
常见的处理RAII类的copy行为的方法有2种:
1.禁止copy;
2.对资源进行引用计数管理。
条款15 在资源管理类中提供对原始资源的访问
对原始资源的访问可以是显式的也可以是隐式的(重载operator()
),显式的比较安全。
条款17 以独立的语句将new的资源放入智能指针
如果不这样做,如果编译器改变了函数调用的顺序,并且在中间抛出异常则会导致资源泄漏。
条款18 让接口易用而不会被误用
shared_ptr
支持deleter
,在接口返回的时候可以给它绑定好deleter
,以防客户误用资源析构。
条款20 用pass-by-reference-to-const代替pass-by-value
1.使用pass-by-reference-to-const高效且没有对象切割问题;
2.对于内置类型和STL迭代器和函数对象,pass-by-value更好。
条款21 将成员变量声明为private
将成员变量声明为private
,可以保证客户访问的一致性,可细微划分访问控制,使类的设计更有弹性。
只有位于参数列表的参数才是隐式转换的合格参与者。
条款25 特化的swap函数
没理解用处。
条款26
尽可能延后变量定义式的出现时间
避免多余的构造和析构;直接初始化的效率要高于先用默认初始化再赋值。
条款27 尽量少做转型动作
1.尽量用上移虚函数的方法代替转型,因为动态类型推断的实现会使代码很大很慢;
2.尽量使用新式转型,抛弃旧式转型。
在类层次间进行上行转换时,dynamic_cast
和static_cast
的效果是一样的;
在进行下行转换时,dynamic_cast
具有类型检查的功能,比static_cast
更安全。
条款28 避免返回handle指向对象内部
const成员函数返回的应该是const 引用,指向对象内部的handle可能会导致对象内部已经析构了而类外的持有者还在使用返回的引用,这将导致不可预测的行为。
条款29 为异常安全而努力是值得的
TODO
条款30 透彻了解inlining的里里外外
inline
一般位于头文件内,因为大部分构建环境都在编译期执行内联,所有编译器需要知道被内联的函数的样子。
templete
通常也位于头文件内,因为它需要在编译期具现化。
条款31 将文件之间的编译依存关系降到最低
尽量以类声明式代替类定义式
程序库头文件应该以“完全且仅有声明式”的形式存在。
可以使用pimpl(pointer to implemention)的模式来降低模块间的耦合度
条款32 确定你的public继承塑造一个“is-a”的关系
切记public继承是一个"is-a"的关系。
条款33 避免覆盖继承来的名称
如果父类定义了2个同名函数,子类也定义了一个同名函数,那么父类的2个函数都会被子类给覆盖。解决方法是显式将父类函数名称暴露给子类作用域:using base::functionName
。
条款34 区分接口继承和实现继承
纯虚函数的意义是派生类不得不继承的函数接口,普通虚函数的意义是为派生类提供可特化 可缺省的函数。
纯虚函数可以有实现,调用方法是 类名:函数名,这个特性的唯一意义在于可以为派生类定义一个默认的函数实现,不用额外去定义一个函数,提高代码的简洁性。
普通函数为派生类提供了一份强制实现。
条款35 虚函数的替代方案
non-virtual-interface(NVI)手法:让客户通过共有非虚函数调用私有虚函数的方式,这个非虚函数就是一个wrapper,这种模式下,基类负责“函数何时被调用”,派生类负责“如何实现功能”。
std::bind
: 将可调用对象和可调用对象的参数进行绑定,返回新的可调用对象(std::function类型,参数列表可能改变),返回的新的std::function
可调用对象的参数列表根据bind
函数实参中std::placeholders::_x
从小到大对应的参数确定。
std::function
:对可调用实体的统一封装.
将virtual函数替换为函数指针成员变量是strategy设计模式的一种分解表现形式。
将继承体系内的virtual函数替换为另一个继承体系内的virtual函数,这是strategy设计模式的传统实现手法。
条款36 绝不重定义继承来的非虚函数
类的非虚函数是一个静态绑定,它的调用取决于指针的类型而不是所指向的内容。
如果给派生类重定义一个继承来的非虚函数,则违反了继承的is-a关系
条款37 绝不重新定义继承而来的缺省参数值
可以使用NVI模式来实现带有默认参数的虚函数。