目录
条款28:避免返回handles(包括引用、指针、迭代器)指向对象内部成分
条款29:为“异常安全”而努力是值得的(尤其注意堆区开辟空间时容易发生异常)
条款26:尽可能延后变量定义式的出现时间
在函数中我们有可能会定义一些不会用到的变量(有可能是在被使用之前发生了异常中断而跳过了使用),当我们定义了含有构造和析构函数的变量时,就必须要承受构造和析构的代价即使变量未被使用,因此应该尽量在要使用变量时才定义变量。
当循环中需要用到变量时,变量定义在循环外时需要一次构造一次析构和n次赋值且变量影响的范围更大(缺点),变量定义在循环内时需要n次构造和n次析构,除非明确赋值成本比构造加析构的成本低且处理的程序是高效率敏感度的,否则变量一律定义在循环内。
条款27:尽量少做转型
c++的转型包括旧式的强制转型和4种c++的新式转型:
旧式转型现在用的最多的是当构造函数为explicit时,强制进行转换
const_cast唯一能移除变量常量性的操作符
dynamic_cast主要用于基类引用或指针安全的向其派生类转型
reinterpret_cast用于执行低级转型
static_cast用来强迫隐式转型(替代旧式转型)
单一对象可能不止拥有一个地址(基类指针指向派生类指针时,不仅有基类地址还有一个指向派生类对象的偏移量),我们也不能假定自己知道变量在内存中的布局,更不能以知道变量在内存中的布局进行类型转换
当我们要在派生类中调用基类的虚函数时更加不能使用类型转换如static_cast来进行向上转换,因为对*this对象的转型是对*this对象的base class成分的副本上进行的并不是对*this对象本身进行,正确的调用方法是通过基类的作用域进行调用基类的同名函数
除了常规类型转换中容易出现未定义的风险,要注意进行dynamic_cast需要进行名称字符串的比较,需要耗费大量时间,应该尽量的避免使用dynamic_cast(直接使用派生类本身的引用指针或对象来调用接口或者是将要调用的接口定义为虚函数都是有效避免dynamic_cast的方法),尤其注意不要在if判断语句中多次使用dynamic_cast转换
如果转型是必要的,试着将它隐藏在某个函数背后。客户随后可以调用该函数,而不需要将转型放进自己的代码中
宁可使用C++新型转型也不要使用旧式转型。前者容易辨识出来,而且也有比较分门别类的执掌
条款28:避免返回handles(包括引用、指针、迭代器)指向对象内部成分
一部分原因是会破坏类的封装,可能使得原本为const的成员函数的行为变得变为不是const的(一个可行的解决方法是返回的类型也改为const)
另一部分的原因是当我们返回指向对象内部成分的handles后,对象有可能会被释放,导致handles指向一个不存在的对象造成未定义的错误。
条款29:为“异常安全”而努力是值得的(尤其注意堆区开辟空间时容易发生异常)
带有异常安全性的函数可以做到:
不泄露任何资源:没有异常安全性的函数可能在获得资源后就发生异常从而导致资源永远不会被释放
不允许数据败坏:没有异常安全性的函数可能在对对象进行释放且还未被重新赋值时发生异常导致对象数据损坏
安全异常函数提供三个不同程度的保证:
基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下(只能保持有效不能保证处于什么状态)
强烈保证:如果抛出异常,程序状态不改变
不抛掷保证:保证能完成预期任务不抛出异常(很难实现)
对于“强烈保证”,往往能够以copy-and-swap(先对要改变的对象做一个副本,对对象进行要进行的操作,如果操作成功再进行交换,如果失败则保持原状),但“强烈保证”并非对所有函数都可实现或具备实现意义
函数提供的“异常安全保证”通常最高只等于其所调用的各个函数的“异常安全保证”中的最弱者
条款30:透彻了解inlining的里里外外
inline函数即内敛函数,
对于普通的函数的使用,编译器会先将函数压缩并存放在栈内的某个地址中,当要使用函数时会通过该地址调用函数。对于inline函数,编译器则不会对函数进行转换,要使用时直接使用函数本身
inline只是对编译器的一个申请,不是强制命令,是否成功转换要取决于编译器
inline函数通常一定被置于头文件中,函数指针、构造函数、析构函数都不是inline的一个好选择
将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二级制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化(inline函数的升级需要重新,而普通函数的升级只需重新连接)
不要因为function templates出现在头文件,就将它们声明为inline
条款31:将文件的编译依存关系降到最低
一般来说类的定义式包括了类的接口(成员函数的声明)和类的实现细目(成员对象的定义),但是这种方式存在着一个缺陷,类的成员对象的类型定义一般来源于某个头文件,如此一来一个类的定义就牵扯到了多个头文件,一旦某个类成员对象的头文件发生改变,那么每一个含有该类的文件都需要重新编译,每一个使用到该类的文件也需要重新编译,因此我们需要将文件间的编译依存关系降到最低
解决这一问题的整体思路是将类的接口和类的实现细目分离,具体的两个手段是Handle classes和Interface classes.
Handle class:对于类中实现细目我们用另一个类进行封装,并在该类中包含一个指向封装类的智能指针,如此一来便可实现接口和实现的分离
Interface class:将类中可能要用到的接口全部定义为纯虚函数放入一个类中并为其定义一个虚析构函数和一个返回指向基类的智能指针的工厂静态函数(用以调用生成对象指针),将这个类作为基类,在派生类中做具体接口定义。当要使用时,我们用基类的静态工厂函数返回指针,以指针来进行具体操作,以此实现接口和实现的分离
程序库头文件应该以“完全且仅有声明式”的形式存在。这种做法无论是否涉及templates都适用p143