在operator=中处理自我赋值

这篇博客探讨了C++编程中自我赋值的问题,包括指针和引用可能导致的隐性自我赋值,以及在资源管理中可能出现的安全隐患。介绍了证同测试作为解决自我赋值的一种方法,但指出其在异常安全方面存在不足。文章进一步阐述了异常安全问题,并提出了通过copy-and-swap技术来确保赋值操作的异常安全性,同时也意外地解决了自我赋值问题。最后,推荐了现代C++的operator=实现方式,即使用pass-by-value并结合swap函数,以提高代码的健壮性和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注意自我赋值

class MyClass{//...};
Myclass m;
m = m; //自我赋值
  • 你可能认为自己不会写出这样的代码,确实,这样显然的自我赋值让我嗤之以鼻。但是因为指针和引用的存在,存在着隐晦的自我赋值。
  • 而且因为继承的存在,Base对象和Derive的指针和引用天然的就可能是同一对象。
MyClass m;
MyClass* p1 = &m;
MyClass* p2 = &m;
*p1 = *p2; //指针的隐性自我赋值

MyClass& r1 = m;
MyClass& r2 = m;
r1 = r2;  //引用的隐性自我赋值
  • 这些已经很不妙了,但是还有更糟糕的情况。

自我赋值安全问题

  • 如果你尝试手动管理资源,可能会掉入一个陷阱:停止使用资源前将其释放。
class MyClassNode{
	vector<int> _v;
	int _num;
public:
//略...
};
class MyClass{
private:
	MyClassNode* _pM;
public:
	MyClass& operator=(const MyClass& m){
		delete _pM; //如果this == &_pM,想一想有什么问题?
		_pM = new MyClass(*m._pM);
		return *this;
	}
};
  • MyClass类中封装一个指针。
  • 在operator=中,我们只需要delete指针,然后拷贝指针即可。但是这段代码是有问题的。倘若你自己给自己赋值比如m = m;
  • 那么你在delete _pM的时候已经将m._pM销毁了,那么你哪来的m._pM用于赋值呢?

证同测试

  • 我们的一种解决方法是加个判断,如果是自我赋值,那么就直接return。
class MyClass{
public:
	MyClass& operator=(const MyClass& m){
		if(&m == this){  //自我赋值,直接return
			return *this;
		}
		else{
			delete _pM;
			_pM = new MyClass(*m._pM);
			return *this;
		}
	}
};
  • 这种手段也叫做证同测试。证同测试可以很好的解决这个问题。

异常安全问题

  • 但是我们仍有一个问题,而且这个问题是个广泛的问题:异常安全。如果我们在new的时候出现异常,但是_pM已经被我们delete掉,我们的operator=函数被迫终止,但是却改变了*this。
  • 实际上,我们只需要略微注意语句的顺序,就可以解决这个问题。比如,使用一个临时对象保持_pM。先赋值,再delete。
class MyClass{
public:
	MyClass& operator=(const MyClass& m){
			MyClass* temp = _pM; //保持_pM的资源
			_pM = new MyClass(*m._pM);
			delete temp;
			return *this;
	}
};
  • 你可能会感到疑惑,因为我去掉了证同测试。你带着疑惑去重新检测,如果是自我赋值…你发现,我们解决了异常安全问题的同时,竟然解决了自我赋值安全问题!!而更让人高兴的是,这两个问题往往是关联着的,也就是说,大部分情况下,解决了异常安全问题就足矣。
  • 你可能会说,但是面对自我赋值时,证同测试的效率会高。哦,确实是的。但是你要知道,证同测试同样需要代价。而operator=调用的越多,代价越大。你应该仔细考量,是否为了自我赋值加上证同测试。

copy-and-swap技术

  • 而解决异常安全问题的另外一种方法就是copy-and-swap技术。也就是我们常说的现代C++的operator=写法。
class MyClass{
public:
	MyClass& operator=(const MyClass& m){
			MyClass temp(m);
			swap(_pM, temp._pM);
			return *this;
	}
};
  • 我们利用temp对象做一个中转。没有异常安全问题,因为构造函数和swap函数都应该是异常安全的。
  • 而还有更简洁的写法,
class MyClass{
public:
	void swap(MyClass& m){//...}
	MyClass& operator=(MyClass m){ //pass-by-value
			swap(m._pM);
			return *this;
	}
};
  • 每错,以值传递,m已经是一份副本。我们直接交换即可。

本文主要来自: 《EffectiveC++》。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值