Effective C++ 读书笔记 条款11:在operator= 中处理"自我赋值"

Core List

1. "自我赋值" 出现的情况

a. w = w;

b. a[i] = a[j];   //  当 i=j 时属于自我赋值

c. *px = *py;    //  当px 和 py 指向同一对象时属于自我赋值


2. 不安全的 "自我赋值" 操作

class Widget {

private:

    Bitmap* pb;

}

Widget& Widget::operator= (const Widget& rhs) {

    delete pb;    //  释放掉本对象成员变量 pb 指向的内存空间

    pb = new Bitmap( *rhs.pb);    //  为 pb 申请新的内存空间,并使用 rhs.pb 内存空间的值进行赋值

    return *this;    //  返回对象自身

}

注:当实现 "自我赋值" 时,由于rhs 和 this 是同一个变量的别名,即pb 和 rhs.pb 指向同一块内存,当 delete pb;执行后, *rhs.pb 将获取非法内容。


改进版一:

Widget& Widget::operator= (const Widget& rhs) {

    if (this == &rhs)  return *this;   // 如果是自我赋值,则直接返回对象本身

    delete pb;    //  释放掉本对象成员变量 pb 指向的内存空间

    pb = new Bitmap( *rhs.pb);    //  为 pb 申请新的内存空间,并使用 rhs.pb 内存空间的值进行赋值

    return *this;    //  返回对象自身

}

注:

a. 如果自我赋值检查为 " if (*this == rhs )",当this 和 rhs 并非指向同一块内存空间,但空间内容相同时,也会错误的认为是自我赋值,此种写法错误。

b. 改进版一仍存在安全问题:若new Bitmap,但又由于 delete pb 已经释放其原来的内存空间,则 pb 将指向一块已经删除的内存空间,引起不安因素。


改进版二:

Widget& Widget::operator= (const Widget& rhs) {

    Bitmap* p = pb;    //  存储pb 原来指向的内存

    pb = new Bitmap( *rhs.pb);    //  令pb 指向 *rhs.pb 的一个副本

    delete pb;    //  当执行到这个步骤时,已经确保 pb 可以指向一个合法的空间

    return *this;    //  返回对象自身

}

注:

a. 如果new Bitmap 发生异常,则 pb 仍然指向原来的内存空间,且空间并未被释放。

b. 这里不再需要“证同测试”,因为自我赋值的情况会导致new Bitmap 异常。

c. 这种方法使得“证同测试” 不必要,因为自我赋值的概率很小,如果对于每一次调用都执行一次“证同测试”是低效的。

d. 考虑上述 operator= 的代码同拷贝构造函数的重复性,当class 中新增成员变量时, operator= 和拷贝构造函数均需要修改。


改进版三:

void swap(Widget& rhs);    // 交换 *this 和 rhs 的数据

Widget& Widget::operator= (const Widget& rhs) {

    Widget temp(rhs);    //  调用拷贝构造函数

    swap(temp);   // 交换数据

    return *this;

}

注:

这里创建一个临时 Widget 对象 temp,该对象使用 rhs 进行初始化,并将该对象同 this 进行交换。


改进版四:

Widget& Widget::operator= (const Widget rhs) {

    swap(rhs);   //  交换数据

    return *this;

}

注:

a. operator= 函数的参数由pass-by-reference 修改为 pass-by-value,临时变量的创建从函数内部,迁移至参数传递的过程中

b. rhs 将在参数传递过程中自动构建,且同 this 进行数据交换。





你可能感兴趣的:(Effective C++ 读书笔记 条款11:在operator= 中处理"自我赋值")