条款11:在operator=中处理自我赋值
自我赋值案例如下:
class Widget {};
Widget w;
w = w;
这看起来有点愚蠢,但它是合法的,所以不要认定客户绝不会那么做!!
但是,有时,这种自我赋值并不是那么一眼就可以看出来的!
a[i] = a[j] //如果i=j相等显然就是自我赋值!
再看:
*px = *py
如果px和py指向同一个东西,这也是自我赋值。实际上这是由于 “别名” 带来的结果!
一般来说:
操作指针指向多个相同类型的对象的情况,这时候需要考虑是否是同一个。
实际上,如果两个对象来自同一个继承体系,它们甚至不需声明为相同类型就可能造成"别名", 因为一个基类的指针或引用可以指向一个派生类的对象。
如下:
class Base {};
class Derived : public Base{};
//这里很显然base和pb有可能其实是同一个对象
void DoSomething(const Base& base, Derived* pb);
下面讲一下用于资源管理类的一个赋值重载的常见操作:
class Bitmap{}; //资源类
class Widget//含有资源的类
{
public:
Widget& operator=(const Widget& rhs)
{
if (this == &rhs)return *this; //自我赋值检测,自我赋值直接返回
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
private:
Bitmap* pb;
};
上述的写法几乎是最常见的写法,虽然可以达到防止自我赋值的问题!
但是不具备"异常安全性",如果delete pb; 已经被执行,但是 pb = new Bitmap(*rhs.pb); 执行失败了,这时候的Widget对象的pb指向未知!
但是我们精心简单的修改一下,它就可以满足我们的异常安全性和防止自我赋值:
Widget& operator=(const Widget& rhs)
{
if (this == &rhs)return *this; //自我赋值检测,自我赋值直接返回
Bitmap* ptemp = pb; //先用指针记录一下资源
pb = new Bitmap(*rhs.pb); //然后new抛出异常,则原本的pb还是保持不变!
delete ptemp;
return *this;
}
同样有另一种更完美的方案可代替上述方案:copy and swap技术。
void Swap(Widget& rhs){} //函数内完成*this 和 rhs的交换数据
Widget& operator=(const Widget& rhs)
{
Widget temp(rhs); //为rhs数据制作一个复件
Swap(temp); //将*this数据和上述复件的数据交换!
return *this;
}
总结:
确保对象自我赋值=有良好行为。包括:来源对象和目标对象地址不是同一个,精心周到的语句顺序、copy-and-swap技术
结尾: 我是航行的小土豆,喜欢我的程序猿朋友们,欢迎点赞+关注哦!希望大家多多支持我哦!有相关不懂问题,可以留言一起探讨哦!
如有引用或转载记得标注哦!