首页 > 编程语言 >Effective C++ Item 11: 在operator=中处理自赋值问题

Effective C++ Item 11: 在operator=中处理自赋值问题

时间:2023-02-06 15:14:07浏览次数:46  
标签:11 Widget Effective rhs C++ Bitmap pb operator 赋值

定义赋值运算符函数需要注意的问题:

  1. 返回值类型声明为引用,并在函数返回前返回*this。因为只有返回引用,才能允许连续赋值。
  2. 传入参数应声明为常量引用,否则从形参到实参会多调用一次拷贝构造函数,降低代码效率。赋值运算符函数不会改变传入的实例的状态,因此传入参数应添加const关键字。
  3. 是否释放实例自身已有的内存。如果忘记分配新内存之前,释放自身已有的空间,程序讲出现内存泄露。
  4. 是否判断传入的参数和this是不是同一个实例。如果是同一个实例,则直接返回this。如果事先不判断就进行赋值,那么在释放实例自身的内存的时候就会导致严重的问题:当*this和传入的参数是同一个实例时,那么一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了。

from 剑指offer

什么是自我赋值?

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

class Widget { ... };

Widget w;
...
w = w;          //赋值给自己

虽然这种做法看起来比较傻,但是这种操作却是合法的。
此外,自我赋值并不是总是可以一眼分辨出来,例如:

  1. 如果i和j具有相同的值时,这就是一个自我赋值。
a[i] = a[j];        //潜在的自我赋值
  1. 如果指针px和py恰巧指向同一个东西,这也是一个自我赋值。
*px = *py;          //潜在的自我赋值

这些并不明显的复制行为,是“别名(aliasing)”所带来的结果。 比如,当基类指针和派生类指针指向同一个基类的对象时,自我赋值就可能发生。

不安全的做法

举个例子,假如建立一个class用来保存一个指针指向一块动态分配的位图(bitmap):

class Bitmap { ... };
class Widget {
    ...
private:
    Bitmap* pb;     //指针,指向一个从heap分配而得的对象
};

接着,下面的operator= 的实现代码,看起来虽然合理,但是在进行自我赋值时并不安全:

Widget& Widget::operator=(const Widget& rhs)    //不安全的operator= 的实现版本
{
    delete pb;     //停止使用当前的bitmap
    pb = new Bitmap(*rhs.pb);       //使用rhs's bitmap的拷贝
    return *this;
}

*this和rhs可能是相同的对象,这样的话delete不仅删除this指针的bitmap,也删除了rhs的bitmap。

问题:不仅自赋值不安全,异常处理也不安全,因为new Bitmap操作是可能产生异常的。

解决方法

1. 传统做法——Identity test, 自赋值检测

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

{

  if (this == &rhs) return *this;   // identity test: if a self-assignment,

                                    // do nothing

  delete pb;

  pb = new Bitmap(*rhs.pb);



  return *this;

}

缺点:new Bitmap的异常安全问题仍存在。

2. 复制pb所指东西之前不要删除pb

实现异常安全通常也能实现自赋值安全。

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

{

  Bitmap *pOrig = pb;               // remember original pb

  pb = new Bitmap(*rhs.pb);         // make pb point to a copy of *pb

  delete pOrig;                     // delete the original pb



  return *this;

}

  • 现在如果new Bitmap抛出异常,pb保持不变。即使不做identity test,它也能处理好自赋值问题。
  • 相对来说,这种方法的效率较低。如果考虑效率问题,可以函数顶部添加自赋值检测代码。但这样做之前,需要考虑自赋值操作发生的频率可能有多高,因为这样做会有一定开销,因为它会导致代码增加以及引入了流程控制分支,会降低运行速度。比如,指令预取、缓存和流水线的效率可能被降低。

3. copy and swap

class Widget {

  ...

  void swap(Widget& rhs);   // exchange *this's and rhs's data;

  ...                       // see Item 29 for details

};



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;

}

标签:11,Widget,Effective,rhs,C++,Bitmap,pb,operator,赋值
From: https://www.cnblogs.com/qiangz/p/17095468.html

相关文章