Effective C++之条款29、30

条款29:为“异常安全”而努力是值得的

    假设有个class用来表现夹带背景图案的GUI菜单。这个class希望用于多线程环境,所以它有个互斥器(mutex)作为并发控制(concurrency control)之用:

class PrettyMenu {
public:
	...
	void changeBackground(std::istream& imgSrc); //改变背景图像
private:
	Mutex mutex;        //互斥器
	Image* bgImage;     //目前的背景图像
	int imageChanges;   //背景图像被改变的次数	
};
void PrettyMenu::changeBackground(istream& imgSrc) {
	lock(&mutex);                //取得互斥器(见条款14)
	delete bgImage;              //摆脱旧的背景图像
	++imageChanges;              //修改图像变更次数
	bgImage = new Image(imgSrc); //安装新的背景图像
	unlock(&mutex);              //释放互斥器
}

    从异常安全性的观点来看,这个函数很糟。异常安全有两个条件,而这个函数没有满足其中任何一个条件。

    当异常被抛出时,带有异常安全性的函数会:

  • 不泄露任何资源。上述代码没有做到这一点,因为一旦“newImage(imgSrc)”导致异常,对unlock的调用就绝不会执行,于是互斥器就永远被把持住了。
  • 不允许数据败坏。如果“new Image(imgSrc)”抛出异常,bgImage就是指向一个已被删除的对象,imageChanges也已被累加,而其实并没有新的图像被成功安装起来。

    解决资源泄露的问题很容易,因为条款13讨论过如何以对象管理资源,而条款14也导入了Lock class作为一种“确保互斥器被及时释放”的方法。

    解决了资源泄露问题,现在我们可以专注于解决数据的败坏了。

    异常安全函数(Exception-safe functions)提供以下三个保证之一:

  • 基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。
  • 强烈保证:如果异常被抛出,程序状态不改变。调用这样的函数需要有这样的认知:如果函数成功,就是完全成功,如果函数失败,程序会恢复到“调用函数之前”的状态。
  • 不抛出(nothrow)保证:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能。作用于内置类型身上的所有操作都提供nothrow保证。这是异常安全码中一个必不可少的关键基础材料。

    异常安全码必须提供上述三种保证之一。我们的抉择是该为我们的每一个函数提供哪一种保证呢。

请记住

  • 异常安全函数即使发生异常也不会泄露资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型和不跑出异常类型。
  • “强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义。
  • 函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的异常安全保证中最弱者。

条款30:透彻了解inlining的里里外外

    一开始先不要将任何函数声明为inline,或至少将inlining实行范围局限在那些“一定成为inline”或“十分平淡无奇”的函数身上。慎重使用inline便是对日后使用调试器带来帮助,不过这么一来也等于把自己推向手工最优化之路。不要忘记80-20经验法则:平均而言一个程序往往将80%的执行时间花费在20%的代码上。这是一个重要的法则,因为他提醒你,作为一个软件开发者,你的目标是找出这可以有效促进程序整体效率的20%代码,然后将它inline或竭尽所能地将它瘦身。

请记住

  • 将多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可是潜在的代码膨胀问题最小化,是程序的速度提升机会最大化。
  • 不要只因为function templates出现在头文件,就将它们声明为inline。
发布了33 篇原创文章 · 获赞 6 · 访问量 566

猜你喜欢

转载自blog.csdn.net/weixin_43519984/article/details/102662920