在operator = 中处理“自我赋值”“自我赋值”时常很容易被修正。
但下面的情况:
a[i] = a[j];
*px = *py;
如果i = j, px和py都指向同一个对象,就会出现“自我赋值”。
有如下类:
class Bitmap { ... };
class Widget
{
public:
Widget& operator = (const Widget& rhs);
...
private:
Bitmap* pb; // 指向堆内存
}
对于如下operator = 实现:
Widget& Widget::operator = (const Widget& rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
看起来合情合理,但是存在问题。
如果出现“自我赋值”,即 *this等于rhs, delete pb等同于delete rhs.pb。
可以加上“证同测试”:
Widget& Widget::operator = (const Widget& rhs)
{
if (this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
这样就具备了“自我赋值安全”,但任存在“异常安全性”问题。
如果new Bitmap导致异常,目标对象将持有一个已被销毁的pb指针。
令人高兴的是,让operator = 具备“异常安全性”往往自动获得“自我赋值安全”的回报。
一种合适办法是:
Widget& Widget::operator = (const Widget& rhs)
{
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
如果Bitmap抛出异常,由于pb还未delete,因此不会有问题。
即使没有“证同测试”, 依然没有问题, 因为使用了pOrig保存了pb。
还有一种叫做copy and swap的技术:
class Widget
{
public:
void Swap(Widget& rhs); // 交换*this和rhs的数据
...
}
Widget& Widget::operator = (const Widget& rhs)
{
Widget temp(rhs); // 为rhs数据制作一件复件
Swap(temp); // 将*this数据和上述复件的数据交换
return *this;
}
其实,这段代码还有优化空间,请移步:copy-and-swap idiom详解和实现安全自我赋值
请记住:
确保当对象自我赋值时operator = 有良好行为。其中技术包括比较“来元对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
确保任何函数如果操作一个以上的对象,而其中多个对象时同一个对象时, 其行为依然正确。