第五章 实现
26、尽可能延后变量定义式的出现时间
太早出现,可能因为下面出现异常,导致构造白白浪费。
延后可以增加程序的清晰度、改善效率。
27、尽量少做转型动作
dynamic_casts 有性能代价,应该尽量避免。绝对要避免“连串动态转型”(cascading dynamic casts)。
如果转型是必要的,试着将它隐藏于某个函数。客户可以条用该函数,而不需要讲转型放进他们的代码内。
宁可使用 C++-style 转型,不要使用旧式转型。前者很容易辨识出来,而且有分门别类的职掌。
28、避免返回 handles 指向对象内部成分
避免返回 handles(包括 reference、指针、迭代器)指向对象内部,可以增加封装型,帮助 const 成员函数的行为像个 const,将发生“虚吊号码牌”(dangling handles)的可能性降至最低。
反之,传出去的 handles 可能让你暴露在“handles ”的风险下。
29、为“异常安全”而努力是值得的
当异常被抛出时,异常安全的函数会:(1)不泄漏任何资源;(2)不允许数据败坏。
异常安全函数(Exception-safe functions)提供这三个保证之一:(1)基本承诺,如果异常抛出,程序内的任何事物仍然保持在有效状态下。(2)强烈保证,如果异常抛出,程序状态不改变。(3)不抛掷(nothrow)保证,承诺绝不抛出异常。
“强烈保证”往往能够以 copy-and-swap 实现出来,但“强烈保证”并非对所有函数都可以实现或具备现实意义。
木桶原理:函数提供的“异常安全保证”通常最高只等于其所调用的各个函数的“异常安全保证”中的最弱者。
30、透彻了解 inlining 的里里外外
将大多数 inlining 限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
不要只因为 function templates 出现在头文件,就将它们声明为 inline。
所有对 virtual 函数的调用(除非是最平淡无奇的)都会使 inlining 落空。
编译器通常不对“通过函数指针而进行的调用”实施 inlining,这意味着对 inline 函数的调用最终是否 inlined 由编译器决定。
构造函数和析构函数往往是 inlining 的糟糕候选,因为他们隐含一些由编译器产生的代码。
inline 函数的风险:它们无法随着程序库的升级而升级,必须重新编译。
31、将文件间的编译依存关系降至最低
支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是 Handle classes 和 Interface classes。
程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法适用于 templates。
(1)如果使用 object references 或 object pointers 可以完成任务,就不要使用 object。
(2)如果可以,尽量以 class 声明式替换 class 定义式。
(3)为声明式和定义式提供不同的头文件。
Java 和 .NET 都不允许在 Interfaces 内实现成员变量或成员函数,但 C++ 可以。
Handle classes 和 Interface classes 有微小的性能损失,但为了降低 classes 之间的耦合性是值得的。如果性能比耦合性重要,才用具象类(concrete )替换它们。