Effective C++ T11:在operator= 中处理 “自我赋值”

38 篇文章 0 订阅
31 篇文章 2 订阅

Effective C++学习笔记总链接

改善程序与设计的55个具体做法学习笔记-每日1条


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

【技巧】

1. 确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap.

2. 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。


自我赋值安全性问题

别名:有一个以上的方法指称某对象。

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

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

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

后果】:如果没有处理自我赋值,在你尝试自行管理资源,可能会掉进“在停止使用资源之前意外释放了它”的陷阱。

看下面的代码,自我赋值并不安全

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

Widget& Widget::operator=(const Widget& rhs) // 不安全的自我赋值
{
	delete pb; // 停止使用当前的Bitmap
	pb = new Bitmap(*rhs.pb); // 使用rhs的Bitmap副本
	return *this;
}

上述代码带来的问题
operator= 函数内的*this(赋值的目的端) 和rhs有可能是同一个对象。此时delete销毁的不只是当前对象的Bitmap,也会销毁rhs的Bitmap
在函数末尾,Widget内持有一个指针指向一个已被删除的对象

自我赋值的解决办法

自我赋值的证同测试

operator= 函数内的最前面的加一个“证同测试,达到“自我赋值”的检验目的

代码如下

Widget& Widget::operator=(const Widget& rhs)
{
	if (this == &rhs)  // 证同测试:
		return *this;  // 如果是自我赋值,就直接返回*this
	delete pb; 
	pb = new Bitmap(*rhs.pb); 
	return *this;
}

但是上述代码的方法仍然存在异常方面的麻烦
如果“new Bitmap”导致异常 (不论是因为分配时内存不足或因为Bitmap的copy构造函数抛出异常),Widget最终会持有一个指针指向一块被删除的Bitmap

这样的指针非常有害,你无法安全地删除它们,甚至无法安全的读取它们。唯一能对它们做的安全事情是付出许多调试能量找出错误的起源。

自我赋值的异常安全性

只需要注意在复制pb所指东西之前别删除pb

较好的自我赋值方法

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保持原状

即使没有证同测试,这段代码还是能够处理自我赋值,因为我们对原Bitmap做了份复制、然后指向新制造的那个复件、删除原Bitmap。或许它不是处理“自我赋值”最高效的办法,但它行得通。

如果你很关心效率,可以预先估计“自我赋值”的发生频率,根据情况将“证同测试” 放到函数起始处。

因为,证同测试,也会使代码变大一些(包括原始码和目标码)并导入一个新的控制流分支,都会降低执行速度

copy and swap 技术

替换方案,同样可行,copy and swap 技术,与异常安全性有密切相关(条款29)

class Widget
{
	void swap(Widget& rhs); // 交换*this 和 rhs 的数据
};

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

这个主题的另一个变奏曲

Widget& Widget::operator=(Widget rhs)
{
	swap(rhs); // 将*this数据和上述副本的数据进行交换
	return *this;
}
  • 某class的copy assignment 操作符可能被声明为“以by value 方式接受实参”
  • 以by value方式传递东西会造成一份副本(条款20)

Scott Meyers 认为它为了伶俐巧妙牺牲了清晰性。

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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值