EC读书笔记系列之6:条款11 在operator=中处理自我赋值

时间:2024-07-22 11:03:20

记住:

★确保当对象自我赋值时operator=有良好行为。有三种方法:比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap技术

★确定任何函数若操作一个以上对象,而其中多个对象是同一个对象时,其行为仍然正确

------------------------------------------------------------------------------------

潜在的自我赋值举例:

a[i] = a[j];  //若i和j相同,便是自我赋值

*px = *py;  //若px和py恰巧指向同一个东西

这些潜在的自我赋值是别名(所谓别名就是有一个以上的方法指称某对象)带来的结果。实际两个对象只要来自于同一个继承体系,他们甚至不需要声明为相同类型就可能造成“别名”,∵一个base class的reference或pointer可以指向一个derived class对象。

举例:下面的operator=代码,表面合理,但自我赋值时不安全

 class Bitmap {...};
class Widget { ...
private:
Bitmap *pb; //含有指针或引用的类在写copying函数时就要注意!本能!
}; Widget& Widget::operator=( const Widget& rhs ) { delete pb; //停止使用当前的bitmap
pb = new Bitmap( *rhs.pb );
return *this;
}

上面存在的问题:*this和rhs有可能是同一个对象。这样delete就不只是销毁当前对象的bitmap,它也销毁rhs的bitmap。

解决方案一:比较“来源对象”和“目标对象”的地址(证同测试),经典解法,初级!!!

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

     if( this == &rhs )
return *this; //证同测试,是自我赋值的话就不做任何事 delete pb;
pb = new Bitmap( *rhs.pb );
return *this;
}

此版可行,但仍存在异常方面的麻烦:若new Bitmap导致异常(有可能因为分配时内存不足或因为Bitmap的copy constructor抛出异常),Widget最终会持有一个指针指向一块被删除的Bitmap。

解决方案二:手工排列语句顺序

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

     Bitmap *pOrig = pb;  //记住原先的pb
pb = new Bitmap( *rhs.pb );
delete pOrig;
return *this;
}

这段代码好处有二:

异常安全: 若new Bitmap失败,pb保持原状;

避免自我赋值:∵对原Bitmap做了一份复件、删除原Bitmap、再指向新制造的那个复件。

此种方法可能不是最高效的办法!!!但行得通。

解决方案三:使用copy-and-swap技术

 class Widget {

     ...
void swap( Widget &rhs ); //交换*this和rhs的数据,详见条款29
...
}; Widget& Widget::operator=( const Widget& rhs ) { Widget temp( rhs ); //为rhs数据制作一份复件---此即copy
swap( temp ); //将*this数据和上述复件的数据交换--此即swap
return *this;
}

方案三还有个改进版:

 Widget& Widget::operator=( Widget rhs ) { //注意这里是传值,∴rhs是被传对象的副本

     swap( rhs );        //将*this数据和上述复件的数据交换
return *this;
}

此版将copying动作从函数本体内移至函数参数构造阶段可令编译器有时生成更高效的代码

一个面试题目:写出String类的赋值运算符

class string{

    public:
string& operator=( string rhs ){ swap( rhs );
return *this;
} void swap( string& rhs ){ std::swap( _data, rhs._data );
} private:
char* _data;
}