C++ 之 重载赋值操作符


下面是一个基类 Bitmap 和派生类 Widget, Widget 中定义了一个私有类型 (private) 指针 pb

class Bitmap { ... };

class Widget {
    ...

private:
    Bitmap *pb; // ptr to a heap-allocated object
};

  当在 Widget 类中重载赋值操作符 "=" 时,需要考虑以下几个方面

1  链式赋值 (chain of assignments)

  整数 15 首先赋值给 z,得到新值的 z 再赋值给 y,接着得到新值的 y 最后再赋值给 x,如下所示:

int x, y, z;

x = y = z = 15; // chain of assignments

  上述链式赋值相当于如下代码:

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 分配新内容,再用 delete 释放如下代码的内容,如下所示:当 "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

本文永久更新链接地址

相关内容

    暂无相关文章