“自我赋值”发生在对象被赋值给自己:
- class Widget{...};
- Widget w;
- ...
- w =w;//赋值给自己
看起来有点蠢,但它合法。赋值动作并不总是那么可被一眼看出来,例如:
a[i] = a[j];//潜在的自我赋值
*px = *py;//潜在的自我赋值
这些并不明显的自我赋值,是“别名”带来的结果:所谓“别名”就是“有一个以上的方法指称某对象”。
如果你尝试自行管理资源,可能会掉进“在停止使用资源之前意外释放了它”的陷阱。假设你建立一个类用来保存一个指针指向一块动态分配的位图:
- class Bitmap{...};
- class Widget
- {
- ...
- private:
- Bitmap *pb;//指针,指向一个从heap分配而的的对象
- };
- //下面是operator=代码的实现。表明上看起来合理,
- //但自我赋值出现时并不安全
- Widget& Widget::operator=(const Widget& rhs)//一份不安全的operator=实现
- {
- delete pb;//停止使用当前的bitmap,
- pb = new Bitmap(*rhs.pb);//使用rhs 的 bitmap副本
- return *this;
- }
这里的自我赋值问题是,operator=函数内的*this和rhs有可能是同一个对象。如果是同一个对象,那delete就不只是销毁当前对象的bitmap,它也销毁rhs的比bitmap。在函数末尾,Widget--它原本不该被自我赋值动作改变的--发现自己持有一个指针指向一个已删除的对象!
欲阻止这种错误,传统做法是藉由operator=最前面的一个“证同测试”达到“自我赋值”的检查目的:
- Widget& Widget::operator=(const Widget& rhs)
- {
- if(this==&rhs) return *this;
- delete pb;
- pb = new Bitmap(*rhs.pb);
- return *this;
- }
这个版本仍然存在异常方面的麻烦,如果new Bitmap导致异常(不论是因为分配内存不足或因为Bitmap的copy构造函数抛出异常),Widget最终会持有一个指针指向一块被删除的Bitmap,这样的指针有害。你无法删除它们,无法读取它们,唯一能对它做的安全的事情是付出许多调试找出错误起源。
“许多时候一群精心安排的语句就可以到处异常安全的代码”
以下代码,我们只需要注意在赋值旁边所指东西之前别删除pb:
- Widget& Widget::operator=(const Widget& rhs)
- {
- Bitmap * pOrig = pb; //记住原先的pb
- pb = new Bitmap(*rhs.pb);//令pb指向*pb的一个副本
- delete pOrig;//删除原先的pb
- return *this;
- }
现在如果new Bitmap抛出异常,pb保持原状,即使没有证同测试,这段代码还是能够处理子午赋值,因为我们对原bitmap做了一份副本,删除原bitmap,然后指向新制造的那个副本。它或许不是处理自我赋值的最高效办法,但它行得通。
copy-and-swap
...
1.确保当对象自我赋值时operator=有良好行为。其中包括比较“来源对象”和“目标对象”的地址,精心周到的语句顺序,以及copy-and-swap。
2.确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。