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
,它也销毁rhs
的 Bitmap
。
在函数末尾,`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=有良好行为。
- 确定任何函数如果操作一个以上对象,而其中多个对象是同一个对象时,其行为仍然正确。