operator= 中的“自我赋值”(Effective C++ 学习笔记详解11)

本文详细探讨了C++中operator=处理自我赋值的情况,指出自我赋值可能导致资源意外释放的问题。通过证同测试可以避免这种错误,但为了实现异常安全,通常需要复制原对象并确保即使在异常情况下也能保持对象状态的完整性。此外,通过传值调用赋值操作符可以由编译器优化代码,尽管这可能牺牲代码的清晰性。重点在于确保在对象自我赋值时,operator=的行为是正确的。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

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

自我赋值发生在对象被赋值给自己,

class widget { ... };
widget w;
...
w= w;                     //赋值给自己
a[i] = a[j];              //潜在自我赋值(i=j)
*px = *py;                //潜在自我赋值(px和py相同)

如果尝试自行管理资源,可能会掉进“在停止使用资源之前意外释放了它”的陷阱。

假设你建立一个class用来保存一个指针指向一块动态分配的位图( bitmap) :

class Bitmap { ... };
class widget
{
private:
    Bitmap* pb;           //指针,指向一个从heap分配而得的对象

};

但自我赋值操作并不总是安全的,它也不具备异常安全性。

Widget& Widget::operator=(const Test& rhs)  //不安全的operator=
{ 
  delete p;                //停止使用当前的bitmap   
  p = new Bitmap(*rhs.p);  //使用当前的rhs’s bitmap的复件(副本)             
  return *this;
}

这里的自我赋值问题是,operator=函数内的 *this(赋值的目的端)和rhs有可能是同一个对象。果真如此delete就不只是销毁当前对象的 Bitmap,它也销毁rhsBitmap

在函数末尾,`widget——它原本不该被自我赋值动作改变的——发现自己持有一个指针指向一个已被删除的对象!

欲阻止这种错误,传统做法是藉由operator=最前面的一个“证同测试”达到“自我赋值”的检验目的:

Widget& Widget::operator=(const Test& rhs)
{  // 传统使用 
    if(this == &t) return *this; 
    //如果是自我赋值,就不执行任何操作;
    delete p;
    p = new Bitmap(*rhs.p);
    return *this;
}

改进,让 operator=具备“异常安全性”往往自动获得“自我赋值安全”的回报。

Widget& Widget::operator=(const Test& rhs)
{     
     Bitmap *org = p;            //记住原先的p      
     p = new Bitmap(*rhs.p);     //令p指向 *p的一个复件(副本)  
     delete org;                 //删除原先的p    
     return *this;
}

现在,如果“newBitmap"抛出异常,p(及其栖身的那个widget)保持原状。即使没有证同测试,这段代码还是能够处理自我赋值,因为我们对原bitmap做了一份复件、删除原bitmap、然后指向新制造的那个复件。它或许不是处理“自我赋值”的最高效办法,但它行得通。

operator=函数内手工排列语句(确保代码不但“异常安全”而且“自我赋值安全”)的一个替代方案是,使用所谓的copy and swap技术。

class widget {
	...
void swap (Widget& rhs);              //交换*this和rhs的数据;详见条款29
    ...
};
widget& widget::operator= (const widget& rhs)
{
    widget temp (rhs) ;               //为rhs数据制作一份复件(副本)
	swap(temp) ;                      //将*this数据和上述复件的数据交换。
	return *this;
}

这个主题的另一个变奏曲乃利用以下事实:

(1)某class 的赋值操作符可能被声明为“以 by value方式接受实参”;

(2)以by value方式传递东西会造成一份复件/副本(见条款20):

widget& widget::operator=(Widget rhs) //rhs是被传对象的一份复件(副本)
{
	swap (rhs);                      //注意这里是pass by value.
    return *this;                    //将*this 的数据和复件/副本的数据互换
}

虽然牺牲了清晰性,然而将“copying动作”从函数本体内移至“函数参数构造阶段”却可令编译器有时生成更高效的代码。

  • 确保当对象自我赋值时operator=有良好行为。
  • 确定任何函数如果操作一个以上对象,而其中多个对象是同一个对象时,其行为仍然正确。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值