Effective-cpp-#10&11

Have assignment operators return a reference to *this

为什么

其实返回void,返回对象本身等都是可以的,返回引用是为了一下两个原因:

  • 允许连续赋值
  • 防止返回对象时因为调用拷贝构造函数和析构函数带了不必要的开销(而且如果对象没有自定义拷贝构造函数,可能会产生错误)

场景

  • 连续赋值如下,因此为了实现连续赋值,必须返回一个引用指向自身,这样后面的赋值才能生效;
1
2
int x, y, z;
x = y = z = 15;//等价于x = (y = (z = 15))
  • 有些场景下,必须返回指向自身的引用
1
2
3
4
5
6
7
8
9
10
11
12
class Widget{
public:
...
Widget& operator+=(const Widget& rhs){//此举是因为该操作符本身的意义就是自增,然后返回自身值
...
return *this;
}
Widget& operator=(const Widget& rhs){
...
return *this;
}
}

Handle assignment to self in operator=

动机

client在操作时很可能会出现,自我赋值的情况,比如:

1
2
a[i] = a[j]; // i == j时
*px = *py; //px与py恰好指向同一对象时

这种情况下,往往会出现安全性缺失的问题

1
2
3
4
5
6
7
8
9
10
class Bitmap{};
class Widget{
public:
Widget& operator=(const Widget& rhs){
delete pb;//如果是自我赋值,删除pb之后会连rhs里的内容也删除了
pb = new Bitmap(*rhs.pb);
return *this;
}
Bitmap* pb;
};

方法

  • 检查合法性
1
2
3
4
5
6
Widget& operator=(const Widget& rhs){
if (&rhs == this) return *this;//如果是自我赋值,不做任何操作
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}

但这个版本仍具有安全问题,如果new时抛出了异常,这样this会指向一块被删除了的内存;

  • 保证异常安全性
1
2
3
4
5
6
7
Widget& operator=(const Widget& rhs){
Bitmap* temp = pb;
pb = new Bitmap(*rhs.pb);
//即便new出现了异常,但由于我们生成了副本指针,因此能够保证this指向有效内存并且影响自我赋值的安全性
delete temp;
return *this;
}

这个方法会降低效率,因为需要拷贝构造的函数

  • copy&swap

这是一种更加高效的做法

1
2
3
4
5
Widget& operator=(const Widget& rhs){
Widget temp(rhs);
swap(temp);//交换temp与this的指针
return *this
}

建议

  • 令赋值操作返回一个reference to *this
  • 确保自我赋值时的安全性,可以采用比较两个对象的地址是否相同,调整操作里的语句顺序,使用copy&swap的方法