侧边栏壁纸
博主头像
如此肤浅

但行好事,莫问前程!

  • 累计撰写 52 篇文章
  • 累计创建 6 个标签
  • 累计收到 6 条评论

目 录CONTENT

文章目录

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

如此肤浅
2022-05-15 / 0 评论 / 0 点赞 / 25 阅读 / 1,680 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-06-02,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

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

问题描述

一般而言如果某段代码操作 pointers 或 references 而它们被用来“指向多个相同类型的对象”,就需考虑这些对象是否为同一个。

实际上两个对象只要来自同一个继承体系,它们甚至不需声明为相同类型就可能造成“别名”,因为一个 base class 的 reference 或 pointer 可以指向一个 derived class 对象:

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

“自我赋值”可能导致掉进 “在停止使用资源之前意外释放了它” 的陷阱:

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

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

这里的自我赋值问题是,operator= 函数内的 *this(赋值的目的端)和 rhs 有可能是同一个对象。果真如此 delete 就不只是销毁当前对象的 bitmap,它也销毁 rhs 的 bitmap。在函数末尾,widget 发现自己持有一个指针指向一个已被删除的对象!

想要阻止这种错误,要在 operator= 中进行检查:

Widget& Widget::operator=(const Widget& rhs)
{
	// 若是自我赋值,则不做任何事
	if(this == &rhs) return *this;
    
	delete pb; 
    pb = new Bitmap(*rhs.pb); 
    return *this;
}

上面改进的版本虽然具备“自我赋值安全性”,但不具备“异常安全性”!!!

因为,如果"new Bitmap()”导致异常(不论是因为分配时内存不足或因为 Bitmap 的 copy 构造函数抛出异常),widget 最终会持有一个指针指向一块被删除的 Bitmapo。这样的指针有害。你无法安全地删除它们,甚至无法安全地读取它们。

解决方式一

然而,当让 operator= 具备“异常安全性”时往往会自动获得“自我赋值安全”的回报。因此,人们对“自我赋值”的处理态度更倾向于不去管它,将注意力放在实现“异常安全性”上。

例如下述代码:

Widget& Widget::operator=(const Widget& rhs)
{
	Bitmap* pOrig = pb; // 记住原先的pb
    pb = new Bitmap(*rhs.pb); //令pb指向*pb的一个副本
    delete pOrig; // 删除原先的pb
    return *this;
}

此时,若"new Bitmap()”导致异常,pb也会保持原状,即使没有自我赋值检测,但还是能处理前面自我赋值所引起的问题。

解决方式二

确保代码不但“异常安全”而且“自我赋值安全”的一个替代方案是使用所谓的 copy and swap 技术。条款29会详细说明。此时 operator= 的撰写方法如下:

class Widget {
...
void swap(Widget& rhs); // 交换*this和rhs的数据;详见条款29
...
};

Widget& Widget::operator=(const Widget& rhs)
{
	Widget temp(rhs); // 为rhs数据制作一份副本
    swap(tmp); // 将*this数据和上述副本的数据交换
    return *this;
}
0

评论区