C++ 自赋值与异常安全问题

https://harttle.land/2015/07/30/effective-cpp-11.html,这个讲的很不错!

1.存在的问题

因为cpp中有指针和引用,它们可以指向同一个变量,所以会存在自赋值的问题。

a[i] = a[j];     //@ 如果i和j有同样的值,这里就是一次自赋值
*px = *py;        //@ 如果px和py指向相同的东西,这里就是一次自赋值 

自赋值一般存在:自赋值安全和异常安全,两个问题,例如:

1.1自赋值安全

Widget& Widget::operator=(const Widget& rhs){
    delete pb;                   // stop using current bitmap
    pb = new Bitmap(*rhs.pb);    // start using a copy of rhs's bitmap
    return *this;                // see Item 10
}

当是自赋值的时候,pb已经先被删除了,那么后面的new就会为空,这是未知的计算。

1.2 异常安全

异常安全是指当异常发生时:

  • 1) 不会泄漏资源
  • 2) 也不会使系统处于不一致的状态。

 通常有三个异常安全级别:基本保证、强烈保证、不抛异常(nothrow)保证。

Widget& Widget::operator=(const Widget& rhs){
    if (this == &rhs) return *this;
    delete pb;                   // stop using current bitmap
    pb = new Bitmap(*rhs.pb);    // start using a copy of rhs's bitmap
    return *this;                // see Item 10
}

这个自赋值安全,但是没有异常安全,如果new处出现了异常,那么pb仍旧指向空。

2.解决办法

2.1 通过调整语句顺序

Widget& Widget::operator=(const Widget& rhs){
    Bitmap *pOrig = pb;               // remember original pb
    pb = new Bitmap(*rhs.pb);         // make pb point to a copy of *pb
    delete pOrig;                     // delete the original pb
    return *this;
}

这里用pO指向原来的动态空间,在new时如果失败了抛出bad alloc异常,那么pb仍然指向原来的空间,不会悬空。

通过一个中间变量来实现,而没有使用if的判断,因为可能会影响效率。

其实也可以这样:

    HasPtr & operator=(const HasPtr& hp){
        std::string * t=new std::string(*hp.ps);
        delete ps;
        ps=t;
        i=hp.i;
        return *this;
    }

主要就是申请一个临时变量来指向原来的空间或者是新申请的空间。//delete应该是不会出现异常的吧。

2.2 copy&swap

Widget& Widget::operator=(Widget rhs){
    swap(rhs);                // swap *this's data with
    return *this;             // the copy's
}

注意参数为值拷贝,在cpp459页有讲解,

 总结:

  1. 判断两个地址是否相同
  2. 仔细地排列语句顺序
  3. Copy and Swap

自赋值存在的知识点差不多这些。

3.不在自赋值中的异常安全问题

转自:https://harttle.land/2015/08/27/effective-cpp-29.html

但其实异常安全问题不仅仅存在于自赋值中,上述连接中给的例子,在更新数据成员时,也可能会出现异常安全的问题:

void Menu::changeBg(istream& src){
    lock(&mutex);
    delete bg;
    ++ changeCount;
    bg = new Image(src);
    unlock(&mutex);
}

当要更新bg时,new出现了问题,则mutex资源泄露,bg变为空悬指针。

针对这个问题,当然这个和自赋值问题是不同的,可以将数据成员bg用智能指针来管理,也可以使用copy&swap技术.

3.1 智能指针smart_ptr

class Menu{
    shared_ptr<Image> bg;
    ...
};
void Menu::changeBg(istream& src){
    Lock m1(&m);
    bg.reset(new Image(src));
    ++changeCont;
}

reset函数中会delete掉原来的空间,但是如果new失败了,并不会进入reset函数,不会对bg产生影响。(但也有问题,但之后的叙述不明白。)

3.2 copy&swap

class Menu{
    ...
private:
    Mutex m;
    std::shared_ptr<MenuImpl> pImpl;
};
Menu::changeBg(std::istream& src){
    using std::swap;            // 见 Item 25
    Lock m1(&mutex);

    std::shared_ptr<MenuImpl> copy(new MenuImpl(*pImpl));
    copy->bg.reset(new Image(src));
    ++copy->changeCount;

    swap(pImpl, copy);
}

先对原来的对象做一个拷贝,然后修改拷贝对象,再swap,函数结束时,copy对象及其指向的内存会被释放,不会造成泄露。

原文地址:https://www.cnblogs.com/BlueBlueSea/p/14018994.html