注意自我赋值
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;
_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 *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 = 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){
swap(m._pM);
return *this;
}
};
- 每错,以值传递,m已经是一份副本。我们直接交换即可。
本文主要来自: 《EffectiveC++》。
|