有一个一般化的策略很典型会导致强烈保证,被称为“copy and swap”:为打算修改的对象做一个副本,在那个副本上做一切必要修改。若有任何修改动作抛出异常,源对象仍然保持未改变状态。待所有改变都成功后,再将修改过的副本和原对象在一个不抛出异常的swap中置换
实现上通常是将所有“隶属对象的数据”从原对象放进另一个对象内,然后赋予源对象一个指针,指向那个所谓的实现对象(implementation object,即副本)。对PrettyMenu而言,典型的写法如下:
struct PMImpl{
std::tr1::shared_ptr
int imageChanges;
};
class PrettyMenu{
...
private:
Mutex mutex;
std::tr1::shared_ptr
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
using std::swap;
Lock ml(&mutex);
std::tr1::shared_ptr
pNew->bgImage.reset(new Image(imgSrc)); //修改副本
++pNew->imageChanges;
swap(pImpl, pNew);//置换数据
}
copy and swap策略虽然做出“全有或全无”改变的一个好办法,但一般而言并不保证整个函数有强烈的异常安全性。
如someFunc。使用copy-and-swap策略,但函数还包括对另外连个函数f1和f2的调用:
void somefunc()
{
…
f1();
f2();
…
}
显然,如果f1或f2的异常安全性比“强烈保证”低,就很难让someFunc成为“强烈异常安全”。如果f1和f2都是“强烈异常安全”,情况并不因此好转。毕竟,如果f1圆满结束,程序状态在任何方面都有可能有所改变,因此如果f2随后抛出异常,程序状态和someFunc被调用前并不相同,甚至当f2没有改变任何东西时也是如此。
问题出现在“连带影响”,如果由函数只操作局部状态,便相对容易的提供强烈保证,但是函数对“非局部性数据”有连带影响时,提供强烈保证就困难的多。例如,如果调用f1带来的影响是某个数据库被改动了,那就很难让someFunc具备强烈安全性。另一个主题是效率。copy-and-swap得好用你可能无法(或不愿意)供应的时间和空间。所以,“强烈保证”并不是在任何时候都显得实际。
当“强烈保证”不切实际时,你就必须提供“基本保证”。
你应该挑选“现实可操作”条件下最强烈等级,只有当你的函数调用了传统代码,才别无选择的将它设为“无任何保证”。
1.异常安全函数即使发生异常也不会泄露资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。
2.“强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义。
3.函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。
条款30:透彻了解inlining的里里外外
Inline函数,多棒的点子!它们看起来像函数,动作像函数,比宏好得多,可以调用它们又不需蒙受函数调用所招致的额外开销。你实际获得的比想象的还多,编译器有能力对执行语境相关最优化。然而编写程序就像现实生活一样,没有白吃的午餐。inline函数也不例外,这样做可能增加你的目标码。
如果inline函数的本体很小,编译器针对“函数本体”所产生的码可能比针对“函数调用”所产出的码更小。果真如此,将函数inlining确实可能导致较小的目标码和较高的指令高速缓存装置击中率。
记住,inline只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出,也可以明确提出。隐喻方式是将函数定义于class定义式内,这样的函数通常是成员函数,friend函数也可被定义于class内,如果真是那样,它们也是被隐喻声明为inline。明确声明inline函数的做法则是在其定义式钱加上关键字inline。
Inline函数通常一定被置于头文件内,因为大多数建置环境在编译过程中进行inlining,而为了将一个“函数调用”替换为“被调用函数的本体”,编译器必须知道那个函数长什么样子。
Template通常也被置于头文件内,因为它一旦被使用,编译器为了将它具现化,需要知道哦啊它长什么样子。
Template的具现化与inlining无关。如果你正在写一个template而你认为所有根据此template具现出来的函数都应该inlined,请将此template声明inline;但如果你写的template煤油理由要求它所具现的每一个函数都是inlined,就应该避免将这个template声明为inline。
一个表面上看似inline的函数是否真实inline,取决于你的建置环境,主要取决于编译器。
有的时候虽然编译器有意愿inlining某个函数,还是可能为该函数生成一个函数本体(函数指针,构造函数,析构函数)。
对 程序开发而言,将上述所有考虑牢记在新很是重要,但若从纯粹实用观点出发,有一个事实比其它因素更重要:大部分调试器面对inline函数都束手无策。
这使我们在决定哪些函数该被声明为inline而哪些函数不该时,掌握一个合乎逻辑的策略。一开始先不要将任何函数声明为inline,或至少将inlining施行范围局限在那些“一定成为inline”或“十分平淡无奇”的函数身上。
请记住:
将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,是程序的速度提升机会最大化。
不要只因为function templates出现在头文件,就将它们声明为inline。
条款31:将文件间的编译依存关系降至最低
请记住:
支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classed和Interface classes。
程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否涉及templates都适用。