标签:
下面是一个基类 Bitmap 和派生类 Widget, Widget 中定义了一个私有类型 (private) 指针 pb
class Bitmap { ... }; class Widget { ... private: Bitmap *pb; // ptr to a heap-allocated object };
当在 Widget 类中重载赋值操作符 "=" 时,需要考虑以下几个方面
1 链式赋值
首先要考虑的是,链式赋值 (chain of assignments) 的情况,如下所示:
int x, y, z; x = y = z = 15; // chain of assignments
整数 15 首先赋值给 z,得到新值的 z 再赋值给 y,接着得到新值的 y 最后再赋值给 x
x = (y = (z = 15));
为了实现链式赋值,函数的返回值须是一个实例自身的引用,也即 *this; 同理,重载其它的复合赋值运算符 (如 +=, -=, *=, /=),也必须在函数结束前返回 *this
Widget& Widget::operator=(const Widget& rhs) { delete pb; // stop using current bitmap pb = new Bitmap(*rhs.pb); // start using a copy of rhs‘s bitmap return *this; }
2 自赋值
其次要考虑的是,关于自赋值 (self-assigment) 的情况,虽然显式的自赋值并不常见,但潜在的自赋值仍需注意
Widget w; ... w = w; // explict assignment to self a[i] = a[j]; // potential assignment to self *px = *py; // potential assignment to self
解决方法是,在函数内加一个 if 语句,判断当前实例 (*this) 和传入的参数 rhs 是不是同一个实例,也即判断是不是自赋值的情况
如果是自赋值,则不作任何处理,直接返回 *this;如果不是自赋值,首先释放实例自身已有内存,然后再分配新的内存,如下所示:
Widget& Widget::operator=(cosnt Widget& rhs) { if (this == &rhs) return *this; // identity test: if a self-assignment, do nothing delete pb; pb = new Bitmap(*rhs.pb); return *this; }
3 异常安全
上例中,假如在分配内存时,因内存不足或 Bitmap 的拷贝构造函数异常,导致 "new Bitmap" 产生异常 (exception),则 pb 指向的是一个已经被删除的 Bitmap
因此,除了链式赋值和自赋值外,还需要考虑异常安全的情况,如下代码所示: 假如 "new Bitmap" 抛出一个异常,pb 指针并没有改变
Widget& Widget::operator=(cosnt Widget& rhs) {
if (this == &rhs) return *this; // identity test
Bitmap *pOrig = pb; // remember original pb pb = new Bitmap(*rhs.pb); // make pb point to a copy delete pOrig; // delete the original pb return *this; }
如果不考虑效率的问题,那么即使没有对自赋值进行判断的 if 语句,其后面的语句也足以应付自赋值的问题
4 拷贝-交换
上例中,因为效率的问题,保留了 if 语句,但实际上,因为自赋值出现的概率很低,所以上述代码看似“高效”,其实并不然
最常用的兼顾自赋值和异常安全 (exception safety) 的方法是 “拷贝-交换” (copy-and-swap),如下所示:
Widget& Widget::operator=(const Widget& rhs) { Widget temp(rhs); // make a copy of rhs‘s data std::swap(*this, temp); // swap *this‘s data with the copy‘s return *this; }
上述代码使用的是标准库的 swap 函数,当然也可以自定义 swap 函数
小结:
1) 重载类赋值操作符,首先考虑链式赋值 -- 函数返回 *this,其次考虑自赋值和异常安全 -- “拷贝-交换”
2) 被重载的类赋值操作符 "=" 必须定义为成员函数,其它的复合赋值操作符 (如 "+=", "-=" 等) 应该被定义为成员函数
参考资料:
<Effective C++_3rd> item 10, 11
<剑指 offer> 2.2.1
标签:
原文地址:http://www.cnblogs.com/xinxue/p/5448020.html