C++ 之 重载赋值操作符

  Widget 中,有一个 Bitmap 型指针  pb

class Bitmap;

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

1  重载 “op=” 

  在 Widget 类中重载 “=” 时,需考虑以下方面

1.1  链式赋值

  整数 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;
}

1.2  自赋值

 
其次要考虑的凡,关于自赋值的图景,虽然显式的自赋值并无广,但暧昧的隐式自赋值仍欲小心

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;
}

1.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); // 注意:"." 的优先级高于 "*"
    delete pOrig;  // delete the original pb

    return *this;
}

   如果未考虑效率的问题,那么即没有针对自赋值进行判断的 if
语词,其背后的口舌为可应付自赋值的题材

 

2  拷贝-交换

  上例被,因为效率的题材,保留了 if
语句,但实际上,因为自赋值出现的票房价值很没有,所以上述代码看似“高效”,其实并不然

  最常用之兼顾自赋值和怪安全 (exception safety) 的不二法门是 “拷贝-交换”
(copy-and-swap),如下所示:

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;
}

2.1  std::swap

  std::swap 属于标准算法,其落实如下:

namespace std 
{
    template<typename T>    // typical implementation of std::swap
    void swap(T& a, T& b)   // swaps a's and b's values
    {
        T temp(a);
        a = b;
        b = temp;
    }
}

  以上有三个拷贝:首先拷贝 a 给
temp,然后拷贝 b 给
a,最后拷贝 temp 给 b

2.2  Widget::swap

  对于 Widget 类,实现两个 Widget
对象的值交换,只需要互换 Bitmap *pb 即可,这称为 pimpl (pointer to
implementation)

  首先,定义一个 swap 公有成员函数,如下:

void Widget::swap(Widget& other)
{
    using std::swap;
    swap(pb, other.pb);  // to swap Widgets, swap their pb pointers
}

  然后,模板特例化 std::swap 函数,调用上面的 swap 函数,实现指针互换

namespace std 
{
    template<>     // revised specialization of std::swap
    void swap<Widget>(Widget& a, Widget& b)        
    {
        a.swap(b); // to swap Widgets, call their swap member function
    }
}

 

3  智能指针

  综上所述,重载赋值操作符,需要考虑链式赋值、自赋值和特别安全,颇为繁琐

  一个简化方法是,在 Widget 类中声称一个智能指针

class Widget {
    ...
private:
    std::unique_ptr<Bitmap> pBitmap; // smart pointer
};

  这,重载 “op=”,则就需要考虑链式赋值

Widget& Widget::operator=(const Widget& rhs) // copy operator=
{
    *pBitmap = *rhs.pBitmap;  // "." 的优先级高于 "*"

    return *this;
}

  理论及应有可行,尚未在实际项目被验证 (留待后续测试…)

 

小结:

1) 重载类赋值操作符,首先考虑链式赋值 — 函数返回
*this,其次考虑从今赋值和坏安全 — “拷贝-交换”

2) 考虑写一个未扔大的 swap 函数 (consider support for a non-throwing
swap
)

3) 被重载的类似赋值操作符 “op=”
必须概念也成员函数,其它的复合赋值操作符 (如 “+=”, “-=” 等)
应该深受定义也成员函数

4) 类中使用智能指针,可大大简化重载赋值操作符 “op=” 的贯彻

 

参考资料:

 <Effective C++_3rd> item 10, 11, 25

 <剑指 offer> 2.2.1

 <Effective Modern C++> item 22