class Widget { ... }; Widget w; ... w = w; // 1. 能通过编译,但是明显不合理! ... a[i] = a[j]; // 2. ij可能相同 *px = *py; // 3. 二者可能相同或有继承关系
上面的三种情况,都是给自己赋值,要在赋值时检查。
class Bitmap { ... }; class Widget { ... private: Bitmap *pb; }; // 既无赋值安全性,又无异常安全性的函数 Widget& Widget::operator=(const Widget& rhs) { delete pb; // 未检查是否自我赋值就释放了资源 pb = new Bitmap(*rhs.pb); // 如出现异常,pb就指向了无效内存 return *this; }
改正的办法:
1> 检查自赋值情况:
// 至少做到了赋值安全性 Widget& Widget::operator=(const Widget& rhs) { if (this == &rhs) return *this; // identity test delete pb; ...
2> 改流程:
// 赋值安全、异常安全,都考虑到了,只是在自赋值的时候效率低些 Widget& Widget::operator=(const Widget& rhs) { Bitmap *pOrig = pb; pb = new Bitmap(*rhs.pb); // 若出现异常也不会影响pb,安全 delete pOrig; // 前面都成功了,再释放原来的资源 return *this; }
如果想把 identity test 加上,那么还要考虑自赋值的频率。频率小的话反而会降低效率,因为会影响CPU的prefetching、caching和pipelining。
3> copy and swap
class Widget { ... void swap(Widget& rhs); ... }; Widget& Widget::operator=(const Widget& rhs) { Widget temp(rhs); swap(temp); return *this; }
有时为了优化效率,而采用传值的方式:
Widget& Widget::operator=(Widget rhs) { swap(rhs); return *this; }