关于C++的赋值运算符的重写,effective C++上已经有足够详细的描述,但是对于拷贝交换技术只是简单的提及,作者对此的看法是不提倡。我认为事实上拷贝交换技术还是非常有学习和应用的必要的,其关键在于,把一切编译器可以完成的工作完全交给编译器去做,而不是由我们去手工实现,从而避免了很多错误。
什么时候需要copy-swap?
构造一个类去管理另外一个类时,需要遵循一个原则( The Rule of Three ),拷贝构造函数,赋值函数,析构函数,如果显示的实现其中一个,其他的都需要显示实现。
原则是什么?
copy-swap是解决方案,可以很好地协助赋值运算符实现两件事:避免代码重复,并提供强大的异常保证。
工作原理?
从概念上讲,它通过使用拷贝构造函数的功能来创建数据的本地副本,然后使用交换功能获取复制的数据,将旧数据与新数据交换来工作。然后,临时副本将销毁,并随身携带旧数据。我们剩下的是新数据的副本。
为了使用copy-swap,我们需要三件事:
- 一个有效的拷贝构造函数
- 一个有效的析构函数
- 一个自定义的交换函数,不能用std::swap,因为该函数实现中调用了拷贝构造和复制函数,且交换函数不抛异常
示例
class Test
{
public:
Test(int a, int b):m_nA(a),m_pB(new int(b))
{
cout << "构造:m_a:" << m_nA << " " << *m_pB << endl;
}
Test(const Test& obj)
{
this->m_nA = obj.m_nA;
this->m_pB = new int(*obj.m_pB);
}
Test& operator=(const Test& obj)
{
if (this != &obj)//避免自我赋值
{
delete m_pB;
this->m_nA = obj.m_nA;
this->m_pB = new int(*obj.m_pB);//可能失败
}
return *this;//返回*this的引用
}
~Test()
{
if (m_pB)
{
cout << "析构:m_a:" << m_nA << " " << *m_pB << endl;
delete m_pB;
m_pB = nullptr;
}
}
private:
int m_nA;
int* m_pB{nullptr};
};
可以看到我们的代码几乎是对拷贝构造函数和析构函数的完全复制,此外,上述代码虽然完成了自赋值的验证,但并未保障异常安全。一旦new失败,原this对象的m_pB已经被删除,因此会引发异常。
下面给出另一种写法:
void swapEx(Test& obj)
{
using std::swap;
swap(this->m_nA, obj.m_nA);
swap(this->m_pB, obj.m_pB);
}
Test& operator=(const Test& obj)
{
if (this != &obj)//避免自我赋值
{
Test tmp(obj);
swapEx(tmp);
}
return *this;
}
上述第一个方法事实上是致命的。在不考虑继承关系的复杂情况下,如果更改类A,添加数据成员,我们在修改其它构造/析构函数的同时,也必须修改赋值运算符。copy and swap技术则可以做到完全规避这一点,此外,所有调用工作由编译器自动完成,无需再做任何额外操作。
该技术的核心就是不再使用引用作为赋值运算符参数,形参将直接是对象,这样的写法将会使编译器自动调用拷贝构造函数,由于拷贝构造函数的调用,异常安全将在进入函数体之前被避免(若拷贝失败则什么都不会发生)。经过swap后的对象在离开函数体后会自动销毁,因此也就自动调用了析构函数。文章来源:https://www.toymoban.com/news/detail-410728.html
参考链接:https://blog.csdn.net/hiwubihe/article/details/116667884
参考链接:https://blog.csdn.net/feifeiiong/article/details/77866579文章来源地址https://www.toymoban.com/news/detail-410728.html
到了这里,关于Copy and Swap技术-安全自我赋值的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!