在C ++中复制构造函数和=运算符重载:是一个常用函数吗?
由于复制构造函数
MyClass(const MyClass&);
和一个=运算符重载
MyClass& operator = (const MyClass&);
有几乎相同的代码,相同的参数,只有不同的返回,有可能有一个共同的function,他们都使用?
是。 有两个常用选项。 其中之一 – 我不build议 – 从显式拷贝构造函数中调用operator=
MyClass(const MyClass& other) { operator=(other); }
然而,提供一个好的operator=
对于处理旧的国家和自我分配所引起的问题是一个挑战。 而且,即使所有的成员和基地都被分配到other
它们也会被默认初始化。 这甚至可能不适用于所有的成员和基地,甚至在有效的情况下,它在语义上是多余的,并且实际上可能是昂贵的。
越来越stream行的解决scheme是实现operator=
使用复制构造函数和交换方法。
MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }
甚至:
MyClass& operator=(MyClass other) { swap(other); return *this; }
swap
函数通常很容易编写,因为它只是交换内部的所有权,不必清理现有状态或分配新资源。
复制和交换习惯用法的优点在于它自动地自行分配安全,并且 – 如果交换操作是非抛出的 – 也是强烈的exception安全的。
为了强有力的exception安全,一个“手写”赋值运算符通常不得不在分配受让人的旧资源之前分配一个新资源的副本,以便在发生exception时分配新资源,旧状态仍然可以返回。 所有这一切都是免费的,而且通常是复制和交换,但是通常更复杂,因此容易出错,从头开始。
要注意的一件事是确保交换方法是一个真正的交换,而不是使用复制构造函数和赋值运算符本身的默认std::swap
。
通常使用成员swap
。 std::swap
工作原理是'所有基本types和指针types的保证。 大多数智能指针也可以换成一个无丢包保证。
复制构造函数执行初始化用作原始内存的对象。 赋值运算符OTOH用新值覆盖现有的值。 比以往任何时候都要耗费大量的资源(例如记忆)和分配新的资源。
如果两者之间有相似之处,那就是赋值操作符执行破坏和复制构build。 一些开发人员曾经实际上通过就地销毁来实施分配,然后进行布局复制。 但是,这是一个非常糟糕的主意。 (如果这是在派生类的赋值过程中调用的基类的赋值操作符呢?)
现在通常认为的典型习惯用法是使用swap
作为查尔斯build议:
MyClass& operator=(MyClass other) { swap(other); return *this; }
这使用复制构造(注意other
被复制)和销毁(它在函数的末尾被销毁),并且它也按照正确的顺序使用它们:销毁前的构造(可能失败)(不能失败)。
有什么困扰我的:
MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }
首先,当我的思想“复制”时,阅读“交换”一词刺激了我的常识。 另外,我质疑这个幻想的目标。 是的,构build新的(复制的)资源的任何exception都应该在交换之前进行,这似乎是一种安全的方式,确保所有新数据在投入使用之前都已经被填充。
没关系。 那么,交换之后发生的exception呢? (当临时对象超出范围时,旧资源被破坏)。从作业用户的angular度来看,操作失败,除非没有。 它有一个巨大的副作用:副本确实发生。 只有一些资源清理失败了。 即使从外部看来操作失败,目标对象的状态也被改变了。
所以,我build议不要用“交换”来做更自然的“转移”:
MyClass& operator=(const MyClass& other) { MyClass tmp(other); transfer(tmp); return *this; }
还有临时对象的构造,但是下一个立即的动作是在移动之前释放目的地的所有当前资源(并且使它们不会被双重释放)来源的资源。
我build议{构build,破坏,移动}而不是{构build,移动,破坏}。 这是最危险的举动,是在所有其他事情得到解决之后最后一次采取的行动。
是的,销毁失败在这两种scheme中都是一个问题。 数据被破坏(当你没有想到的时候被复制)或者丢失(当你没有想到的时候被释放)。 丢失比败坏更好。 没有数据比坏数据好。
转移而不是交换。 无论如何,这是我的build议。