Effective C++读书笔记——item11(自赋值)
自赋值相关问题
- 自赋值情况示例
- 明显的自赋值如
w = w
,还有不太容易辨别的情况,像a[i] = a[j]
(当i
和j
值相同)、*px = *py
(当px
和py
指向同一对象)等,这些是由别名(有多种引用对象的方式)造成的,尤其在涉及引用、指针操作同类型多个对象以及继承体系中基类和派生类对象引用、指针转换时要考虑自赋值可能。
- 明显的自赋值如
- 自赋值在资源管理类中的隐患
- 以
Widget
类持有指向动态分配位图的裸指针pb
为例,若operator=
实现时未考虑自赋值情况,像最初不安全的实现:先delete pb
再new Bitmap(*rhs.pb)
,当*this
和rhs
是同一个对象时,会导致删除了不该删除的资源(同时破坏了rhs
的资源),最终使对象持有指向已删除对象的 “有毒” 指针。 -
Widget& Widget::operator=(const Widget& rhs) // unsafe impl. of operator= { delete pb; // stop using current bitmap pb = new Bitmap(*rhs.pb); // start using a copy of rhs's bitmap return *this; // see Item 10 }
- 以
- 传统防止自赋值错误的方法及局限
- 传统方法是在
operator=
开始处通过if (this == &rhs) return *this;
进行一致性检测,若为自赋值则直接返回。但此方法只解决了自赋值安全问题,对于之前提到的operator=
版本,其还存在异常不安全的问题,例如new Bitmap
表达式引发异常时,会导致对象持有指向已删除位图的指针,后续无法安全处理。
- 传统方法是在
兼顾异常安全与自赋值安全的方法
- 调整语句顺序
- 通过先保存原指针指向的数据(如
Bitmap *pOrig = pb;
),再进行新数据的拷贝(pb = new Bitmap(*rhs.pb);
),最后删除原数据(delete pOrig;
)的语句顺序调整,能在保证异常安全的同时处理自赋值情况,即使new Bitmap
抛出异常,对象原有状态也不会被破坏,且无需一致性检测,但可能不是最有效率的做法。 Widget& Widget::operator=(const Widget& rhs) { Bitmap *pOrig = pb; // remember original pb pb = new Bitmap(*rhs.pb); // make pb point to a copy of *pb delete pOrig; // delete the original pb return *this; }
- 通过先保存原指针指向的数据(如
- 使用 “copy and swap” 技术
- 涉及类中定义
swap
函数交换数据,一种实现是先创建参数对象的副本(Widget temp(rhs);
),再通过swap(temp)
交换*this
和副本的数据;另一种变种是将operator=
参数以传值方式接收(Widget& Widget::operator=(Widget rhs)
),函数内直接swap(rhs)
交换数据,后者在一定程度上可能让编译器产生更有效率的代码,但清晰度可能稍受影响。 -
class Widget { ... void swap(Widget& rhs); // exchange *this's and rhs's data; ... // see Item 29 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; } Widget& Widget::operator=(Widget rhs) // rhs is a copy of the object { // passed in — note pass by val swap(rhs); // swap *this's data with // the copy's return *this; }
- 涉及类中定义
总结与注意事项
在编写代码时要确保operator=
等操作多个对象的函数在对象自赋值或对象相同时行为良好,处理自赋值问题的技巧包括比较源和目标对象地址、关注语句顺序、使用 “copy and swap” 等方法,并且要根据实际情况权衡效率、代码清晰度等因素来选择合适的方式。