条款 11:在 operator= 中处理“自我赋值“

自我赋值

有时候我们提供的类客户有可能进行以下误操作:

class Widget { ... };
Widget w;
...
w = w; // 给自己赋值

a[i] = a[j]; 	// 潜在的自我赋值
*px = *py; 	// 潜在的自我赋值

即进行自我赋值。
 如果两个对象来自同一个继承结构,甚至不需要将它们声明为同一类型,因为基类引用或指针可以引用或指向派生类类型的对象:

class Base { ... };
class Derived : public Base { ... };
void doSomething(const Base& rb, Derived* pd); // rb和*pd可能是同一个对象

 如果试图自己管理资源,可能会陷入这样的陷阱:在使用完资源之前不小心释放了资源。

class Bitmap { ... };
class Widget {
public:
    Widget&  operator=(const Widget& rhs)...
private:
    Bitmap* pb; // 指向一个从堆中分配的对象
};
Widget& Widget::operator=(const Widget& rhs) // 不安全的版本
{
    delete pb; 			// 停止使用当前的 bitmap
    pb = new Bitmap(*rhs.pb); 	// 开始使用rhs的bitmap的一个副本
    return *this; 			// 见条款10
}

*this(赋值的目标)和rhs可以是同一个对象,导致最终指向一个已经被删除的对象。
防止这种错误的传统方法是在operator=的顶部检查是否是对自己赋值:

Widget& Widget::operator=(const Widget& rhs)
{
    if (this == &rhs) return *this; // 如果是自我赋值,什么也不做
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

但是它还有异常安全性的问题:如果“new Bitmap”表达式产生异常(因为没有足够的内存用于分配,或者因为Bitmap的拷贝构造函数抛出异常),将最终指向已删除的Bitmap对象。
幸运的是,使operator=具有异常安全性,通常也会使它具有自赋值安全性。

Widget& Widget::operator=(const Widget& rhs)
{
    Bitmap* pOrig = pb; 		// 记住原先的pb
    pb = new Bitmap(*rhs.pb); 	// 让pb指向*rhs.pb的副本
    delete pOrig; 			// delete原先的pb
    return *this;
}

如果“new Bitmap”抛出异常,pb(以及它所在的Widget)保持不变。

拷贝和交换

除了手动排序operator=中的语句,以确保实现异常安全和自我赋值安全,另一种选择是使用称为“拷贝和交换”的技术。

class Widget {
    ...
    void swap(Widget& rhs); 	// 交换*this和rhs的数据;
    ... 			// 详见条款29
};
Widget& Widget::operator=(const Widget& rhs)
{
    Widget temp(rhs); // 复制rhs的数据
    swap(temp); 	// 将*this的数据与副本的数据交换
    return *this;
}

类的复制赋值操作符可以声明为按值接受实参,按值传递某项内容会生成该实参的副本:

Widget& Widget::operator=(Widget rhs) // pass-by-value,rhs是对象的副本
{ 
    swap(rhs); // 将*this的数据与副本的数据交换
    return *this;
}

总结

  • 当对象被赋值给自身时,确保operator=表现良好。技术途径包括:比较源对象和目标对象的地址、仔细的语句排序、copy-and-swap。
  • 确保在两个或多个对象相同(是同一个对象)的情况下,函数的行为仍然正确。
  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值