《Effective C++》Item11:在operator=中处理自我赋值

所谓的“自我赋值”发生在对象被赋值给自己时:

class Object { /* .. */ }

int main()
{
    Object o;
    o = o; //自我赋值。
    return 0;
}

这种写法看上去没有任何意义,但是它是可以通过编译的;最关键的是,这种操作往往不会显示地发生,例如:

int main()
{
    Object o;
    //...
    Object *p1 = &o;
    Object *p2 = &o;
    //...
    *p1 = *p2
    return 0;
}

在上面这种情况(及其所有类似的情况)中,如果两个指针或引用指向同一个对象,那么这种赋值操作同样是属于自我赋值;更有甚者,两个对象只要是来自于同一个继承体系,那么即便它们的类型不同,这两个对象也很有可能压根就是一个(即它们拥有相同的地址):

class Base { /* ... */ };
class Derived : public Base { /* ... */ };

//这里的rb和pd可能指向的是同一个对象。
void function(const Base &rb, Derived *pd);

如果遵循RTTI的忠告,那么我们会使用对象来管理资源,而且可以确定所谓的“资源管理对象”在拷贝发生时会做出正确的举措;然而如果我们尝试自己管理资源(例如,我们正在编写资源管理类),就可能会掉进“在停止使用资源之前就意外地将其释放”这样的错误陷阱当中。

例如,我们建立了一个类来表示“位图”:

class BitMap { /* ... */ };
class Widget
{
    BitMap *bitMap;
public:
    //...
    Widget &operator=(const Widget &other);
};

下面是一份operator=的实现代码,表面上看起来很合理,但是在自我赋值的时候并不安全:

Widget &Widget::operator=(const Widget &other)
{
    delete bitMap;
    bitMap = new BitMap(*(other.bitMap));
    return *this;
}

这里的赋值问题是,operator=函数内的*thisother很有可能是同一个对象。一旦这种情况发生,那么delete bitMap操作就销毁了这个函数中唯一一个可用的BitMap对象,因而这个对象将不再持有合法的BitMap对象指针。

解决这个问题的方法是在一开始就判断*thisother是否是同一个对象:可以通过检查它们的地址是否相同来判断:

Widget &Widget::operator=(const Widget &other)
{
    if(this != &other) {
        delete bitMap;
        bitMap = new BitMap(*(other.bitMap));
    }
    return *this;
}

这样做行得通。

现在来讨论另一个问题:异常安全性。这个新的实现方案仍然存在“异常安全”的麻烦:现在假设new BitMap操作失败了,那么从今往后当前对象将会持有一个非法的BitMap对象指针。

所谓异常安全性是指以下两个方面:

  • 不泄露资源。
  • 不破坏数据。

幸运的是,很多时候一组精心安排的代码可以满足异常安全性的要求。例如:

Widget &Widget::operator=(const Widget &other)
{
    if(this != &other) {
        BitMap *pTmp = bitMap;
        bitMap = new BitMap(*(other.bitMap));
        delete pTmp;
    }
    return *this;
}

现在,如果new BitMap抛出了异常,那么原对象将不会出现问题。

还要介绍一种非常精妙的方案,它可以实现operator=的自我赋值安全性以及异常安全性,并且实现方案非常直观:

class BitMap { /* ... */ };
class Widget
{
    BitMap *bitMap;
public:
    void swap(Widget &other) 
    { 
        BitMap *pTemp = bitMap;
        bitMap = other.bitMap;
        other.bitMap = pTemp;
        //...
    }

    //...
    Widget &operator=(const Widget &other)
    {
        Widget tmp(other);
        swap(tmp);
        return *this;
    }
};

通过构造一个other对象的副本,然后在将当前对象和这个副本进行数据交换的方式,可以完美地避开上述两个问题。这种技巧被称为CAS(Copy And Swap)。

【注意】

  • 确保当对象赋值时有良好的行为,可以利用精心编排的语句,或者是CAS
  • 确定当函数操作一个以上的对象引用,而且这些对象引用可能指向同一个对象时,其行为仍然正确。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值