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 进行数据交换。