设为首页 加入收藏

TOP

《Effective C++》学习笔记(八)(一)
2015-07-20 17:46:57 来源: 作者: 【 】 浏览:13
Tags:Effective 学习 笔记

?

?

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

看完这个条款,第一感觉就是之前写的代码多么具有风险。

先看一个书上的例子,假设有个class用来表现夹带背景图案的GUI菜单,这个class也要用于多线程环境当中,所以我们考虑用了一个互斥器(mutex)作为并发控制(concurrency control)之用:

?

class PrettyMenu
{
public:
    ...
    void changeBackground(std::istream& imgSrc); //改变图片背景
    ...
private:
    Mutex mutex;       //互斥器
    Image* bgImage;    //目前的背景图像
    int imageChangesCounts; //背景图像被改变的次数
};

// 下面是PrettyMenu的changeBackground函数的一个可能实现:
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
    lock(&mutex);          //取得互斥器
    delete bgImage;        //摆脱旧的背景图像
    ++imageChangesCounts;  //修改变更次数
    bgImage_ = new Image(imgSrc); //安装新的背景图像
    unlock(&mutex);        //释放互斥器
}
从“异常安全性”来看,这个函数很糟糕。“异常安全”有两个条件,而这个函数没有满足其中任意一个条件。

?

1)不泄露任何资源:一旦“new Image(imgSrc)”导致异常,对unlock的调用就绝对不会执行,于是互斥器就永远锁住了。

2)不允许数据破坏:如果“new Image(imgSrc)”抛出异常,bgImage就是指向了一个已被删除的对象,且imageChanges也被累加,而其实没有新的图像被成功安装。

解决资源泄露很简单,,我们可以用资源管理类来确保互斥器锁定后一定会释放。

异常安全函数提供以下三种保证之一:

基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。

强烈保证:如果异常被抛出,程序状态不改变。

不抛掷(nothrow)保证:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能。

一般而言我们会想提供最高保证,但是我们很难在C part of C++中调用一个完全没有可能抛出异常的函数。任何使用动态内存的东西一旦没有足够内存满足要求,都会抛出一个bad_alloc异常。

?

class PrettyMenu
{
    ...
    std::tr1::shared_ptr
bgImage; }; void PrettyMenu::changeBackground(std::istream& imgSrc) { Lock(&mutex); //获得互斥器并确保它稍后被释放 bgImage.reset(new Image(imgSrc)); ++imageChangesCounts; }上面是我们优化后的代码,但是该函数也只能提供“基本的异常安全保证”。因为如果Image构造函数抛出异常,有可能输入流的读取记号已被移走,而这样的搬移对程序其余部分是一种可见的状态改变。
有个一般化的设计策略可以达到这个目的,这个策略被称为“copy and swap”,俗话说“名如其人”,这个原则其实就是:为你打算修改的对象保存一个副本,然后在该副本上修改。若修改过程中发生异常,问题不大,因为原对象状态没有被改变。修改动作完成以后进行“副本与原对象互换”操作。

?

?

struct PMImpl
{
    std::tr1::shared_ptr
bgImage; int imageChangesCounts; }; class PrettyMenu { ... void changeBackground(std::istream& imgSrc) { using std::swap; Lock m1(&mutex); std::tr1::shared_ptr pNewImpl(new PMImpl(*pImpl)); //make copy pNewImpl->bgImage_.reset(new Image(imgSrc));//handle copy ++pNew->imageChangesCounts; swap(pImpl,pNewImpl); //swap } private: Mutex mutex_; std::tr1::shared_ptr pImpl; }; 我们注意到copy-and-swap的关键在于“修改对象数据副本,然后在一个不抛异常的函数中将修改后的数据和原件置换”,因此必须为每一个即将被改动的对象做一个副本,那得耗用你
可能无法供应的时间和空间。

?

这是一个很实际的问题:我们都希望提供“强烈保证”;当它可被实现时你的确应该提供它,但“强烈保证”并非在任何时刻都显得实际。

当强烈保证不切实际的时候,你就必须提供基本保证。

在实际的开发当中你可以为某些函数提供强烈保证,但效率和复杂度带来的成本会使得你不得不去放弃它,万一实际不可行,使你退而求其次地只提供基本保证,任何人都不该因此责难你。对许多函数而言,“异常安全性之基本保证”是一个绝对通情达理的选择。
?

总结:

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

?

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

inline函数,你可以调用它们又不蒙受函数调用招致的额外开销,编译器就会对函数本体执行语境相关最优化,而大部分编译器不会对着一个非inline函数调用动作执行如此之最优化。

由于对inline函数的每一个调用都以函数本体替换之,所以说这样就可能增加你的目标代码,在一台有限的机器上,过度热衷inlining会造成程序体积太大,即使拥有虚拟内存,inline造成的代码膨胀亦会导致额外的换页行为,降低指令高速缓存装置的击中率,以及伴随而来的效率损失。

以上是inline函数的优缺点,所以我们依旧要辩证性的看待inline函数。

不能忽视的一点是,inline函数仅仅是一个申请,而不是强制命令,所以它是会可能被编译器驳回的。这个申请可以隐喻提出,也可以明确提出。

隐喻提出就是将函数定义于class定义式内,这样的函数通常是成员函数或者是friend函数。如下:

?

class Person
{
public:
    ...
    int age() const
    {
        return theAge; // 一个隐喻的inline申请
    }
    ...
private:
    int theAge;
}
明确声明就是在函数之前加关键字inline。例如标准的max template往往这样实现出来:

?

?

template
  
   
inline const T& std::max(const T &a, const T &b)
{
    return a < b ? b : a;
}
  

?

inline函数通常一定要被置于头文件内,其在大多数C+

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇有意思的进程创建函数fork()的问题 下一篇Leetcode 细节实现题 Spiral Matr..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·如何在 C 语言中管理 (2025-12-25 03:20:14)
·C语言和内存管理有什 (2025-12-25 03:20:11)
·为什么C语言从不被淘 (2025-12-25 03:20:08)
·常用meta整理 | 菜鸟 (2025-12-25 01:21:52)
·SQL HAVING 子句:深 (2025-12-25 01:21:47)