【Effective C++】读书笔记 Part5 实现
条款29:为异常安全而努力是值得的
当异常被抛出时,待哟异常安全性的函数会:
- 不泄漏任何资源。
- 不允许数据败坏。
异常安全函数提供以下三个保证之一
- 基本承诺。如果异常被抛出,程序内的任何事物仍然在有效状态下。没有任何对象或数据结构会因此而败坏。所有对象都处于一种内部前后一致的状态,但是程序中哦个对象的现实状态不可预料。
- 强烈保证。如果异常被抛出,程序状态不会被改变。调用这样的函数只有如下两种情况:函数调用成功,就是完全成功,如果函数调用失败,程序会回复到“调用函数之前”的状态。
- 不抛出异常。函数保证绝不抛出异常,因为它们总能够完成它们所承诺的功能。
对于任何函数,都应该尽量保证异常安全,注意下面亮点:
- 不要为了表示某件事情发生而改变对象状态,除非那件事情已经真的发生了。
- copy-swap策略,往往能够帮助我们实现“强烈保证”的异常安全,但是“强烈保证”并非堆所有函数都可实现或具备现实意义,有因为效率和复杂度的关系,“基本承诺”的异常安全就已经够用了。
- 函数提供的的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。
条款30:透彻了解inlining的里里外外
1. 基础知识
Inline函数会给我们带来许多的好处,它们看起来像函数,动作函数,比洪好的多,可以调用它们但是又不需要承受函数调用所招致的额外开销。编译器最优化设计通常被设计用来浓缩那些“不含函数调用”的代码,所以当使用inline函数时候,编译器就有能力对它执行语境相关最优化。
inline函数通常是将相应的函数本体直接展开在调用点,而舍弃了函数调用。如果函数本体过大,这就会导致目标代码(object code)增大,造成代码膨胀。但是如果inline函数本体很小,编译器针对“函数本体”所产生的目标代码可能比针对“函数调用”产生的目标代码所产出的目标代码更小,同时也拥有了inline函数所带来的效率。
2. inline函数的inlining
inline关键字可以用来修饰普通函数和类的成员函数。对于inline普通函数通常需要将函数定义及其实现放置在头文件中,因为对于大多数build environment而言,都是在编译过程中进行inlining(inline展开),所以对于一个inline函数或者是inline成员函数,都需要定义在头文件内,从而保证编译器在编译阶段就已经拥有了inline函数的定义。
3. 编译器视情况来选择是否inlining
除此之外,inline函数只是向编译器提出一个申请,编译器可以选择忽略。大多数编译器都会拒绝将太过复杂的函数(如带有循环或者递归)进行inlining,而对于所有虚函数也都会拒绝inlining。
由于对于所有的虚函数而言,显然只有在运行时候才能够确定具体执行的函数本体,但是对于inline函数而言,则是在编译阶段就需要了解到函数的具体定义。显然对于virtual函数,编译器无法将其inlining。
除此之外,inline函数(非成员函数)其作用于一般都是在文件作用域内,因为inline函数如果都能够在调用处展开,编译器也没有必要对此函数创建符号,并且在代码段中存储函数的二进制指令(函数本体)。
但是,有些情况下,虽然编译器能够对函数进行inlining,但是也会生成一个函数本体。
如当我们对一个inline函数进行取地址的时候,这时候就强迫编译器生成一个相应的outlined函数本体。
另外,如果我们通过一个函数指针来调用inline函数,编译器往往不会将其inlining,因此根据调用函数的方式不同,编译器会视情况来决定是否inlining。
inline void f(){……}//假设编译器会inline函数f
void ( *pf)()=f;//指向函数的指针
f();//这个调用会被inlined,因为是正常调用
pf();//这个调用可能不会被inlined,因为它是通过函数指针达成
4. inline函数与构造和析构函数
构造函数和析构函数往往都不适合作为inline函数。虽然在许多情况下,构造函数的函数体都可能十分小,或者为空。
但是看看C++标准对于对象被构建和销毁时候所保证的的事情:
- 当你使用new的时候,动态创建的对象被构造函数自动初始化 。
- 当你使用delete的时候,对应的析构函数将被调用。
- 当你创建一个对象的时候,其每一个base class及其每一个成员变量都会被自动构造。
- 当你销毁一个对象的时候,反向顺序的析构行为会自动发生。
- 如果有个异常在构造函数内抛出,该对象已经构造好的那一部分会自动析构。
等等。
这就注定了本来可能函数体非常小的构造函数和析构函数,在经过编译器的处理后就会变得非常复杂,显然就不适合将其进行inlining。
5. inline函数与函数升级
对于程序库中如果不存在non-inline函数,那么客户端程序一般只需要重新链接即可,并不需要重新编译。
如果客户端程序动态链接程序库,那么可能就无需做任何改变就可直接使用升级了版本的程序库。
但是如果将程序库设计为inline函数会给我们的客户端程序带来一定的冲击。主要是客户端程序中,将包含我们程序库中的inline函数的展开,因而如果程序库升级了inline函数,那么客户端代码也必须要重新编译。这个过程量就会显著增大。
6. inline函数与调试
最后一点要注意的是,inline函数往往无法和调试器完美配合,因为在代码执行中,inline函数已经被展开,显然你无法在一个已经不存在的函数设置断点。
7. 小结
- 请将大多数inline函数限制在小型,被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使得程序的速度提升机会最大化。
- 不要只是因为function template出现在头文件中,就将它们声明为inline.