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

返回值类型声明为引用,并在函数返回前返回*this。因为只有返回引用,才能允许连续赋值。

传入参数应声明为常量引用,否则从形参到实参会多调用一次拷贝构造函数,降低代码效率。赋值运算符函数不会改变传入的实例的状态,因此传入参数应添加const关键字。

是否释放实例自身已有的内存。如果忘记分配新内存之前,释放自身已有的空间,程序将出现内存泄露。

是否判断传入的参数和this是不是同一个实例。如果是同一个实例,则直接返回this。如果事先不判断就进行赋值,那么在释放实例自身的内存的时候就会导致严重的问题:当*this和传入的参数是同一个实例时,那么一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了。

from 剑指offer

二、什么是自我赋值?

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

class Widget { ... };

Widget w;

...

w = w; //赋值给自己

虽然这种做法看起来比较傻,但是这种操作却是合法的。

此外,自我赋值并不是总是可以一眼分辨出来,例如:

如果i和j具有相同的值时,这就是一个自我赋值。

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

如果指针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;

}

五、如果类成员含有的unique_ptr指针,而没有raw pointer,那么在它的赋值运算符函数中不处理自赋值问题会有什么问题吗?

如果类成员只有std::unique_ptr,而没有裸指针,那么在赋值运算符函数中不处理自赋值问题是没有问题的。

原因如下:

std::unique_ptr的赋值操作会自动处理自赋值问题。当一个std::unique_ptr被赋值时,原有的内存将被自动删除,然后才会接管新的内存。

举例来说,假设有以下的代码:

class Foo {

public:

std::unique_ptr ptr;

Foo& operator=(const Foo& other) {

if (this != &other) {

ptr = std::make_unique(*other.ptr);

}

return *this;

}

};

即使不进行自赋值检查,当进行自赋值时,std::unique_ptr的赋值操作也不会导致问题。这是因为std::unique_ptr的赋值操作会首先删除当前的内存,然后再分配新的内存。因此,即使是自赋值,也不会有问题。

然而,这不意味着在所有的情况下都可以忽略自赋值检查。如果类成员是裸指针或者其他需要手动管理内存的资源,那么就必须在赋值运算符函数中处理自赋值问题,以防止内存泄露或者其他问题。