你能编写一个同时处理复制构造函数和复制赋值运算符的通用函数吗?

问题描述 投票:0回答:3

由于复制构造函数

MyClass(const MyClass&);

和 = 运算符重载

MyClass& operator = (const MyClass&);

有几乎相同的代码,相同的参数,只是返回不同,是否有可能有一个共同的函数供它们使用?

c++ variable-assignment copy-constructor c++-faq
3个回答
135
投票

是的。有两种常见的选择。一种 - 通常不鼓励 - 是从复制构造函数显式调用

operator=

MyClass(const MyClass& other)
{
    operator=(other);
}

然而,在处理旧状态和自我分配引起的问题时,提供良好的

operator=
是一个挑战。此外,所有成员和基础都会首先默认初始化,即使它们要从
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
可以工作,并且对于所有基本类型和指针类型都保证“不抛出”。大多数智能指针也可以在不抛出保证的情况下进行交换。


17
投票

复制构造函数对曾经是原始内存的对象执行首次初始化。赋值运算符 OTOH 用新值覆盖现有值。这通常涉及废弃旧资源(例如内存)并分配新资源。

如果两者之间有相似之处,那就是赋值运算符执行破坏和复制构造。一些开发人员过去实际上通过就地销毁然后放置复制构造来实现分配。然而,这是一个“非常”坏主意。 (如果这是在派生类赋值期间调用的基类的赋值运算符怎么办?) 如今通常被认为是规范的习语是使用

swap

,正如 Charles 建议的那样:


MyClass& operator=(MyClass other) { swap(other); return *this; }

这使用了复制构造(请注意,
other

被复制)和销毁(它在函数末尾被销毁)——并且它也以正确的顺序使用它们:在销毁之前构造(可能会失败)(不得失败)。

    


-3
投票

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}。这一举动是最危险的行动,是在一切都解决之后最后采取的行动。

是的,销毁失败在这两种方案中都是一个问题。数据要么被损坏(当您不认为它被复制时被复制)或丢失(当您不认为它被释放时被释放)。丢失总比损坏好。没有数据比坏数据好。

转移而不是交换。无论如何,这就是我的建议。

© www.soinside.com 2019 - 2024. All rights reserved.