由于复制构造函数
MyClass(const MyClass&);
和 = 运算符重载
MyClass& operator = (const MyClass&);
具有几乎相同的代码,相同的参数,并且仅在返回上有所不同,是否可以有一个共同的功能供他们使用?
由于复制构造函数
MyClass(const MyClass&);
和 = 运算符重载
MyClass& operator = (const MyClass&);
具有几乎相同的代码,相同的参数,并且仅在返回上有所不同,是否可以有一个共同的功能供他们使用?
是的。有两种常见的选择。一个 - 通常不鼓励 - 是operator=
从复制构造函数中显式调用:
MyClass(const MyClass& other)
{
operator=(other);
}
然而,operator=
在处理旧状态和自我分配产生的问题时,提供一个物品是一个挑战。此外,所有成员和基础都首先默认初始化,即使它们要分配给 from other
。这甚至可能对所有成员和基都无效,即使它有效,它在语义上也是多余的,并且实际上可能很昂贵。
一个越来越流行的解决方案是operator=
使用复制构造函数和交换方法来实现。
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
swap(tmp);
return *this;
}
甚至:
MyClass& operator=(MyClass other)
{
swap(other);
return *this;
}
一个swap
函数通常很容易编写,因为它只是交换内部的所有权,而不必清理现有状态或分配新资源。
复制和交换习惯用法的优点是它是自动自赋值安全的,并且 - 假设交换操作是无抛出的 - 也是非常安全的异常安全。
为了保证异常安全,“手写”分配操作员通常必须在解除分配受让人的旧资源之前分配新资源的副本,这样如果分配新资源时发生异常,旧状态仍然可以返回到. 所有这些都通过复制和交换免费提供,但通常更复杂,因此容易出错,从头开始。
需要注意的一件事是确保交换方法是真正的交换,而不是std::swap
使用复制构造函数和赋值运算符本身的默认值。
通常使用逐个成员swap
。std::swap
工作,并且所有基本类型和指针类型都保证“不抛出”。大多数智能指针也可以通过不抛出保证进行交换。
复制构造函数对曾经是原始内存的对象执行首次初始化。赋值运算符 OTOH 用新值覆盖现有值。通常情况下,这涉及消除旧资源(例如内存)并分配新资源。
如果两者之间有相似之处,那就是赋值运算符执行破坏和复制构造。一些开发人员过去实际上是通过就地销毁,然后是布局复制构造来实现分配。然而,这是一个非常糟糕的主意。(如果这是在派生类赋值期间调用的基类的赋值运算符怎么办?)
swap
正如查尔斯建议的那样,如今通常被认为是规范的成语正在使用:
MyClass& operator=(MyClass other)
{
swap(other);
return *this;
}
这使用了复制构造(注意other
是复制的)和破坏(它在函数的末尾被破坏)——它也以正确的顺序使用它们:构造(可能失败)在破坏之前(不能失败)。
有件事困扰着我:
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
swap(tmp);
return *this;
}
首先,当我在思考“复制”时阅读“交换”这个词会激怒我的常识。另外,我质疑这个花哨的把戏的目标。是的,构建新(复制)资源的任何异常都应该在交换之前发生,这似乎是一种确保所有新数据在上线之前都已填充的安全方法。
没关系。那么,交换后发生的异常呢?(当临时对象超出范围时旧资源被破坏)从分配用户的角度来看,操作失败了,但它没有。它有一个巨大的副作用:复制确实发生了。只是一些资源清理失败了。即使从外部看来操作失败,目标对象的状态也已更改。
所以,我建议不要“交换”,而是做一个更自然的“转移”:
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
transfer(tmp);
return *this;
}
临时对象的构造仍然存在,但下一个立即行动是在将源的资源移动到它之前释放目标的所有当前资源(并清空,因此它们不会被双重释放)。
我建议使用 {construct, destruct, move } 而不是 {construct, move, destruct}。此举是最危险的动作,是在其他一切都解决后最后采取的动作。
是的,破坏失败在这两种方案中都是一个问题。数据要么已损坏(当您认为它不存在时被复制),要么已丢失(当您认为它不存在时被释放)。丢失总比损坏好。没有数据比坏数据更好。
转移而不是交换。无论如何,这是我的建议。