0.概述
- 确保当对象自我赋值时operator=有良好行为。包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap.
- 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
1.自我赋值
发生在对象被赋值给自己
1.1 显而易见的
class Widget { ... };
Widget w;
...
w = w; //赋值给自己
1.2 潜在自我赋值
如果i和j值相同,就是自我赋值:
a[i] = a[j];
如果px和py恰巧指向同样的东西,也是自我赋值:
*px = *py;
这些潜在自我赋值,是“别名”(aliasing)带来的结果:所谓“别名”就是“有一个以上的方法指称(指涉)某对象”。
一般而言如果某段代码操作pointers 或references用来“指向多个相同类型的对象”,就需考虑这些对象是否为同一个。
两个对象只要来自同一个继承体系,它们甚至不需声明为相同类型就可能造成“别名”,因为一个base class的 reference或pointer可以指向derived class对象:
class Base { ... };
class Derived: public Base { ... };
void doSomething(const Base& rb, // rb and *pd可能指向
Derived* pd); // 同一对象
2.自我赋值带来的问题
可能会在停止使用资源之前意外释放了它,这样将会导致指针指向一个被删除的对象
3.解决方案
3.1 证同测试
Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) return *this;//证同测试,如果是自我赋值,不做任何事
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
3.2 异常安全性
如下代码,只需在复制pb所指东西前别删除pb:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb; //保存原先的pb
pb = new Bitmap(*rhs.pb); //令pb指向*pb的副本
delete pOrig; //删除原先的pb
return *this;
}
这段代码在具备异常安全性的同时也自动获得了“自我赋值安全”,而不需做证同测试(因为我们对原bitmap做了一份复件、删除原bitmap、然后指向新制造的那个复件)。
这或许不是处理“自我赋值”的最高效办法,但行得通。
如果你很关心效率,可以把“证同测试”( identity test)再次放回函数起始处。但这样做会影响程序效率:
- 代码变大
- 导入新的控制流
3.3 copy and swap
确保代码不但异常安全而且自我赋值安全。
class Widget {
...
void swap(Widget& rhs); // exchange *this’s and rhs’s data;
... // see Item29 for details
};
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); // make a copy of rhs’s data
swap(temp); // swap *this’s data with the copy’s
return *this;
}
这种实现基于以下两个事实:
- 某 class 的 copy assignment操作符可能被声明为“以by value方式接受实参”
- 以 by value方式传递东西会造成一份复件/副本