不在状态

2016/9/22 无聊

Posted by WangXiaoDong on September 22, 2016

时间:2016年9月22日 天气:晴:sunny:


Author:冬之晓:angry:
Email: 347916416@qq.com

    今天,天气转晴了。一天,感觉很无聊,这几天一直都很不在状态,今天开始要好好学习了,加油!

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

先举书上的例子:

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

这段代码大致的意思就是改变背景图片,删掉旧的背景图片,记录修改次数,然后创建新的背景图片。考虑到多线程操作,所以这里用了lock和unlock。

但这里会出现问题,因为并不是每次new都会成功的,有可能抛出异常,一旦抛出异常,unlock就没有执行了,这样资源就会一直处于lock状态,而无法继续操作了。另一方面,虽然本次改变背景的操作的失败了,但imageChanges仍然自增了一次,这就不符合程序员设计的初衷了。

有读者就会想,那还不简单,加上try…catch块就行了,像这样:

void PrettyMenu::changeBackground(std::istream& imgSrc)
{
    try
    {
        lock(&mutex);
        delete bgImage;
        bgImage = new Image(imgSrc);
        ++ imageChanges;
unlock(&mutex);
    }
    catch (Exception* e)
    {
        unlock(&mutex);
    }
}

在catch里面写上unlock函数,另外,调换imageChanges的位置,在new之后再对齐自增。这样做固然可以,但回想一下,我们在条款十三和条款十四做了资源管理类,让类的析构函数自动替我们完成这个操作,不是会更好吗?像这样:

class PrettyMenu
{
    …
    shared_ptr<Image> bgImage;
    …
}
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
    Lock m1(&mutex);
    bgImage.reset(new Image(imgSrc));
    ++ imageChanges;
}

这样,即使抛出了异常,锁资源还有imageChanges都保证是异常发生之前的状态。

现在上升到理论的高度,异常安全性要做到:

第一个条件:不泄露任何资源。(上面这个函数当new Image(imgSrc)发生异常的话,unlock(&mutex)就永远不会被执行,所以就资源泄漏了)。

第二个条件:不允许数据败坏。(上面这个函数当new Image(imgSrc)发生异常的话,bgImage就是指向一个已删除的对象)。

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

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

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

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

当你写新代码或修改旧代码,想想如何让它具备异常安全性。首先是“以对象管理资源”,那可阻止资源泄漏。然后是挑选三个“异常安全保证”中的某一个实施于你所写的每一个函数身上。

强烈保证有一种实现方法,那就是copy and swap。原则就是:在修改这个对象之前,先创建它的一个副本,然后对这个副本进行操作,如果操作发生异常,那么异常只发生在这个副本之上,并不会影响对象本身,如果操作没有发生异常,再在最后进行一次swap。

void PrettyMenu::changeBackground(std::istream& imgSrc)
{
    Lock m1(&mutex);
    弄一个临时的tempBgImage
    对tempBgImage进行操作
    swap(tempBgImage, bgImage);
    ++ imageChanges;
}

copy-and-swap策略关键在于“修改对象数据的副本,然后在一个不抛异常的函数中将修改后的数据和原件置换”。它确实提供了强异常安全保障,但代价是时间和空间,因为必须为每一个即将被改动的对象造出副本。另外,这种强异常安全保障,也会在下面的情况下遇到麻烦:

void someFunc()
{
    f1();
    f2();
}

f1()和f2()都是强异常安全的,但万一f1()没有抛异常,但f2()抛了异常呢?是的,数据会回到f2()执行之前的状态,但程序员可能想要的是数据回复到f1()执行之前。要解决这个问题就需要将f1与f2内容进行融合,确定都没有问题了,才进行一次大的swap,这样的代价都是需要改变函数的结构,破坏了函数的模块性。如果不想这么做,只能放弃这个copy-and-swap方法,将强异常安全保障回退成基本保障。

类似于木桶效应,代码是强异常安全的,还是基本异常安全的,还是没有异常安全,取决于最低层次的那个模块。换言之,哪怕只有一个地方没有考虑到异常安全,整个代码都不是异常安全的。

四十年前,满载goto的代码被视为一种美好实践,而今我们却致力写出结构化控制流。二十年前,全局数据被视为一种美好时间,而今我们却致力于数据的封装。十年前,写“未将异常考虑在内”的函数被视为一种美好实践,而今我们致力写出“异常安全码”。时间不断前进,我们与时俱进。

请记住:

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

设计模式(十六)————适配器模式

适配器模式:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作

注意:如果能事先将软件的接口设计为相同,就尽量不用适配器模式,只有确实需要转换接口时才使用!

下面通过打篮球时候需要翻译来举例子:

#ifndef BASKETBALLPLAYER
#define BASKETBALLPLAYER
#include <QtDebug>
#include <QString>

class Player
{
public:
    Player(QString name):_name(name){}
    virtual void Attack() = 0;
    virtual void Defense() = 0;
    virtual ~Player() = default;
protected:
    QString _name;
};
class Forwards final:public Player
{
public:
    Forwards(QString name):Player(name){}
    void Attack() override
    {
        qDebug()<<"前锋"<<_name<<"攻击!";
    }
    void Defense() override
    {
        qDebug()<<"前锋"<<_name<<"防守!";
    }
};
class Center final:public Player
{
public:
    Center(QString name):Player(name){}
    void Attack() override
    {
        qDebug()<<"中锋"<<_name<<"攻击!";
    }
    void Defense() override
    {
        qDebug()<<"中锋"<<_name<<"防守!";
    }
};
class Guards final:public Player
{
public:
    Guards(QString name):Player(name){}
    void Attack() override
    {
        qDebug()<<"后卫"<<_name<<"攻击!";
    }
    void Defense() override
    {
        qDebug()<<"后卫"<<_name<<"防守!";
    }
};

class ForeignCenter
{
public:
    ForeignCenter() = default;
    void TranslateAttack()
    {
        qDebug()<<"翻译后:中锋"<<_name<<"攻击!";
    }
    void TranslateDefense()
    {
        qDebug()<<"翻译后:中锋"<<_name<<"防守!";
    }
    void setName(QString name){_name = name;}
    QString getName(){return _name;}
private:
    QString _name;
};
class Translator final:public Player   //相当于适配器
{
public:
    Translator(QString name):Player(name){_fc = QSharedPointer<ForeignCenter>(new ForeignCenter());_fc->setName(name);}
    void Attack() override
    {
        _fc->TranslateAttack();
    }
    void Defense() override
    {
        _fc->TranslateDefense();
    }
private:
    QSharedPointer<ForeignCenter> _fc;
};

#endif // BASKETBALLPLAYER

#include "basketballplayer.h"

int main(int argc, char *argv)
{
    Player *ch = new Translator("姚明");
    ch->Attack();
    ch->Defense();
    return 0;
}

根据代码可以发现,适配器模式实际上就是在需要的类中保存另一个类的实例,通过调用所需要的接口的函数来从另一个类的实例中找到相关的方法来实现这个接口,完成了不同接口之间的适配。一般在两种不同的接口需要相互调用的时候来构造适配器方便他们之间的相互访问。