Effective C++ 条款11_不止于此

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

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

class Getself {
    
     ... };
Getself w;
...
w = w;				// 赋值给自己
					// 看起来似乎有些愚蠢,但不乏有人这样做

还有一些潜在不容易看出来的自我赋值:

a [ i ] = a [ j ];   // 潜在的自我赋值
// 如果 i = j,那么便是自我赋值

再看:

*ptr = *qtr;   // 潜在的自我赋值
// 如果 ptr 和 qtr 都指向同一个东西,那这就是自我赋值

假设建立一个 class 用来保存一个指针指向一块动态分布的位图(bitmap):

class Bitmap{
    
    ...};
class Getself {
    
    
...
private:
	Bitmap* pb;
};

下面来看一段 operator= 的实现代码:

Getself& Getself::operator= (const Getself& r){
    
    		// 不安全的实现版本
	delete pb;			// 停止使用当前 bitmap
	pb = new Bitmap(*r.pb);  // 使用 r's bitmap 的副本(附件)
	return *this;			// 见条款 10
}

分析:
这里的 “ 自我赋值 ” 问题是,operator= 函数体内的 *this (赋值的目的端)和 r 有可能是同一对象。如果真是如此那就太糟糕了,因为这样的话 delete 掉的就不只是当前对象的 bitmap,它也销毁了 r 的bitmap。在函数末尾,Getself ——他原本不该被自我赋值动作改变的——发现自己持有一个指针指向一个被删除了的对象!

为了阻止这样的错误,可以采用 “ 证同测试(identity test)” 达到 “ 自我赋值 ” 的检验目的:

Getself& Getself::operator= (const Getself& r){
    
    	
	if(this == &r) return *this;  // 如果是自我赋值,就不做任何事
	delete pb;			
	pb = new Bitmap(*r.pb);  
	return *this;			

这样做行的通,但是不好。因为仍然存在异常方面的麻烦(new Bitmap 可能导致异常)。
下面介绍另一种更好的方法:

Getself& Getself::operator= (const Getself& r){
    
    	
	Bitmap* org = pb;			// 记住原先的 pb
	pb = new Bitmap(*r.pb);  	// 令 pb 指向 *pb 的一个附件(副本)
	delete org;					// 删除原先的 pb
	return *this;		

这种方法可以避免 “ new Bitmap ” 抛出异常带来的麻烦,但是效率不如前面一种方法,这时候就要折中考虑了。

还有一种替代的方法,即所谓的 copy and swap 技术。这个技术和 “ 异常安全性 ” 有密切联系,在条款 29 会详细说明。
现在只是了解一下基本实现手法:

class Getself {
    
    
...
void swap (const Getself& r); 		// 交换 *this 和 r 的数据
};
Getself& Getself::operator= (const Getself& r){
    
    
	Getself temp(r);			// 将 r 数据制作一份附件(副本)
	swap(temp);					// 将 *this 数据和上述附件的数据交换
	return *this;
}

最后请记住:

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

猜你喜欢

转载自blog.csdn.net/weixin_48033173/article/details/109034294