Skip to content

Latest commit

 

History

History
111 lines (85 loc) · 3.18 KB

File metadata and controls

111 lines (85 loc) · 3.18 KB

条款11:在operator=中处理“自我赋值”

“自我赋值”发生在对象被赋值给自己时:

class Widget {...};
Widget w;
...
w = w;

这是合法的操作,所以不要认定客户绝不会那么做,此外赋值操作并不总是可容易被看出来,例如:

a[i] = a[j];  // 潜在的自我赋值

如果i和j有相同的值,这便是个自我赋值。再看:

*px = *py;

如果px和py恰巧指向同一个东西,这也是自我赋值。一个基类的指针或引用可以指向同一个派生类对象:

class Base {...};
class Derived : public Base {...};
void doSomething(const Base& rb, Derived* pd); // rb和*pd可能是同一对象

如果你建立一个class用来保存一个指针指向一块内存:

class Bitmap {...};
class Widget {
  ...
private:
  Bitmap* pb;
};

下面是 operator= 实现代码,表面上看起来合理,但自我赋值出现时并不安全:

Widget& Widget::operator=(const Widget& rhs) { 
  delete pb;
  pb = new Bitmap(*rhs.pb);
  return *this;
}

这里的自我赋值问题是,operator= 函数内的 *thisrhs 有可能是同一对象。这样就会导致Widget持有一个指针指向一个已被删除的对象。

想要阻止这种错误,传统做法是使用if判断达到自我赋值检验的目的:

Widget& Widget::operator=(const Widget& rhs) {
  if (this == &rhs) return *this;  // protect against self-assignment

  delete pb;
  pb = new Bitmap(*rhs.pb);
  return *this;
}

但是这两个版本都不具备异常安全性。如果 new Bitmap 导致异常,Widget则会指向一块被删除的Bitmap。你无法安全地删除他们,甚至无法安全地读取它们。

许多时候一些精心能安排的语句就可以实现异常安全(以及自我赋值安全)的代码了。例如以下代码,我们只需要注意在赋值pb所指对象之前不要删除pb:

Widget& Widget::operator=(const Widget& rhs) {
  Bitmap* pOrig = pb;
  pb = new Bitmap(*rhs.pb);
  delete pOrig;
  return *this;
}

现在,如果 new Bitmap 抛出异常,pb将保持原状。这段代码仍能处理自我赋值,因为我们对原对象做了一份副本、删除原Bitmap,然后指向新创建的副本。

operator= 函数内手动排列语句的一个替代方案是,使用copy and swap技术。这个技术由条款29详细说明。先看看其实现手法是什么样子:

class Widget {
  ...
  void swap(Widget& rhs);
  ...
};
Widget& Widget::operator=(const Widget& rhs) {
  Widget temp(rhs);
  swap(temp);
  return *this;
}

另一个方案利用以下事实:(1)某class的拷贝赋值运算符可能被声明为值传递;(2)值传递会造成一份副本。

Widget& Widget::operator=(const Widget& rhs) {
  swap(rhs);
  return *this;
}

这段代码效率更高但可读性较差。

请记住

  • 确保当对象自我赋值时operator=有良好行为。其中技术包括比较源对象和目标对象的地址、精心周到的语句顺序、以及copy and swap。
  • 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。