C++之为“异常安全”而努力是值得的(29)---《Effective C++》

来源:互联网 发布:如何导入数据到excel 编辑:程序博客网 时间:2024/05/21 07:01

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

在进行今天“异常安全”讨论主题之前,我们先来看看如下代码:

class PrettyMenu{public:    ...    void changeBackground(std::istream& imgSrc);    ...private:    Mutex mutex;    Image* bgImage;    int imageChanges;};void PrettyMenu::changeBackground(std::istream& imgSrc){    lock(&mutex);    delete bgImage;    ++imageChanges;    bgImage=new Image(imgSrc);    unlock(&mutex);}   

站在“异常安全性”方面考虑,这个函数特别糟糕!因为:
1)容易泄露资源,如果bgImage=new Image(imageSrc)没有申请资源成功,那么此事后unlock(&mutex)将永远不会执行,这样会导致资源泄露;
2)指向的数据容易是坏块,因为bgImage=new Image(imageSrc)如果抛出异常,那么此时bgImage将不会指向新生成的区域,而继续指向其原来的位置,而其原来的内存区域由于delete bgImage里面没有任何数据,因此bgImage将指向一个早已被删除的对象,造成数据幻影。

即“异常安全性”要求我们满足如下两个方面要求: 1)不泄露任何资源; 2)不允许数据败坏。

那我们的解决办法是什么呢?通过前面所提到的使用对象那个资源管理器即可轻松解决该问题,Lock资源管理类可以保证资源不会泄露,具体可以参考“条款11”中处理自赋值问题的解决办法!解决代码如下:

void PrettyMenu::changeBackground(std::istream& imgSrc){    Lock m1(&mutex);    delete bgImage;    ++imageChanges;    bgImage=new Image(imgSrc);}

刚我们在上面代码中主要侧重于防止资源泄露方面,现在我们主要针对代码坏块方面进行考虑。
异常安全函数提供以下三个保证之一:

1)基本承诺;如果异常抛出,程序内的任何事物仍然保持在有效状态之下,没有任何对象那个或者数据结构会因此而得到败坏,所有对象那个都处于一种内部前后一致的状态; 2)强烈保证;如果异常抛出,程序状态不改变,调用这样的函数需要有这样的认知,如果函数成功,就是彻底调用成功;如果函数失败,程序会恢复到“调用函数之前”的状态,就像数据库中的回滚操作一样,保证数据库的一致性; 3)不抛掷保证:承诺绝不抛出异常,因为它们总是能够完成它们原来承诺的功能,作用于内置类型身上的所有操作都提供nothrow保证,这是异常安全码中一个必不可少的关键基础材料。

针对强烈保证,我们通常有一个设计策略,称之为copy and swap设计策略,原则很简单:为你打算修改的对象做出一份副本,然后在副本上做出一切必要修改,若有任何修改动作抛出异常,原对象任然保持未改变状态,带所有改变都成功之后,再将修改过的那个副本和元对象那个在一个不抛出异常的操作中置换,实际实现通常是将所有“隶属对象的数据”从原对象放进另一个对象那个,然后赋予原对象一个指针,指向那个所谓的实现对象(副本),这种实现手法被称之为“pimpl idiom”,典型实现手段如:

struct PMImpl{    std::trl::shared_ptr<Image> bgImage;    int ImageChanges;};class PrettyMenu{    ...private:    Mutex mutex;    std::trl::shared_ptr<PMImpl> pImpl;};void PrettyMenu::changeBackground(std::istream& imgSrc){    using std::swap;    Lock m1(&mutext);//获得mutex的副本数据    std::trl::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));    pNew->bgImage.reset(new Image(imgSrc));//修改副本    ++pNew->imageChanges;    swap(pImpl,pNew);//置换(swap)数据,释放mutex}

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

阅读全文
0 0
原创粉丝点击