当异常抛出时,带有异常安全的函数会:
不泄露任何资源(条款13:资源管理类)
不允许数据败坏
异常安全函数提供以下三个保证之一:
基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态。然而程序的现实状态不可预料。
强烈保证:如果异常被抛出,程序状态不改变。
不抛掷保证:承诺绝不抛出异常,因为他们总能完成它们原先承诺的功能。
copy and swap会导致强烈保证。原则是为你打算修改的对象(原件)做出一份副本,然后在那副本身上做一切必要修改。若有任何修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换(swap)。
请记住:
异常安全函数(Exception-safefunctions)即使发生异常也不会泄漏资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。
“强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义。
函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。
条款30:彻透了解inlining的里里外外
inline函数,看起来像函数,比宏好得多,可以调用它们又不需蒙受函数调用所招致的额外开销。
inline函数背后整体的整体观念是,将“对此函数的每一个调用”都以函数本体替换之。这样做可能增加目标码的大小。在一台内存有限的的机器上,过度热衷inlining会造成程序体积太大,即使有虚拟内存,inline造成的代码膨胀也会导致额外换页行为,降低指令高速缓存装置的击中率,以及伴随这些而来的效率损失。
inline只是对编译器的一个申请,不是强制指令。这项申请可以隐喻提出,也可以明确提出。隐喻方式是将函数定义于class定义式内:
class Person{ public: ... int age() const { return theAge; } //隐喻申请 ... private: int theAge };
这样的函数通常是成员函数,friend函数也可被定义于class内,如果真是那样,它们也是被隐喻声明为inline。
明确声明inline函数的做法则是在其定义式钱加上关键字inline。例如标准的max template:
template<typename T> inline const T& std::max(const T& a, const T& b){ return a < b ? b : a; }
大部分编译器拒绝将过于复杂(例如带有循环或递归)的函数inlining,而所有对虚函数的调用也都会使inlining落空。因为虚函数直到运行期才确定调用哪个函数,而内联函数意味执行前先将调用动作替换为被调用函数的本体。
一个表面上看似inline的函数是否真是inline,取决于你的建置环境,主要取决于编译器。编译器通常不对“通过函数指针而进行的调用”实施inlining。
构造函数和析构函数往往是inlining的糟糕候选人。(由编译器于编译期代为产生并安插在程序中的代码,可能存在于构造函数和析构函数中)。
程序库设计者必须评估“将函数声明为inline”的冲击:inline函数无法随着程序库的升级而升级。例如f是程序库内的一个inline函数,客户将“f函数本体”编进其程序中,一旦程序库设计者决定改变f,所有用到f的客户端程序必须重新编译。但如果f是non-inline函数,客户端只需重新连接即可;如果是动态链接库,升级版函数甚至可以不知不觉地被应用程序吸纳。
大部分调试器面对inline函数都束手无策,因为你不能在一个不存在的函数内设立断点。
请记住:
将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
不要只因为function templates出现在头文件,就将它们声明为inline。
条款31:将文件的编译依存关系降至最低
如果没有取得其实现代码所用到的class string,Date和Address的定义式,那么class Person无法通过编译。
class Person{ public: Person(const std::string& name, const Date& birthday, const Address& addr); std::string name() const; std::string birthday() const; std::string address() const; ... private: std::string theName; //实现细节 Date theBirthday; //实现细节 Address theAddress; //实现细节 };
所以Person定义文件的最上方可能存在:
这么一来使得Person定义文件和其含入文件之间形成了一种编译依赖关系。如果这些头文件中有任何一个被改变,或者这些头文件依赖的其他头文件有任何改变,那么每一个含入Person class的文件就得重新编译,任何使用Person class的文件也必须重新编译。
Handle classes可以解除接口和实现之间的耦合关系,从而降低文件间的编译依存性。
解除接口和实现之间的耦合关系,从而降低文件间的编译依存性的方法还有Interface classes。
请记住:
支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classed和Interface classes。
程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否涉及templates都适用。