关于变量赋值:C++中的复制构造函数和=运算符重载:可能的公共函数吗?

Copy constructor and = operator overload in C++: is a common function possible?

因为是复制构造函数

1
MyClass(const MyClass&);

且an=操作员过载

1
MyClass& operator = (const MyClass&);

具有几乎相同的代码、相同的参数,并且返回时只有不同的参数,是否可以有一个共同的函数供两者使用?


对。共有两种选择。一种通常不鼓励的方法是从复制构造函数显式调用operator=

1
2
3
4
MyClass(const MyClass& other)
{
    operator=(other);
}

然而,提供一个好的operator=是一个挑战,当它涉及到处理旧的状态和自我分配产生的问题。此外,所有成员和基都会首先初始化默认值,即使它们将从other分配给它们。这甚至可能对所有成员和基都无效,甚至在它有效的情况下,它在语义上是多余的,并且可能实际上是昂贵的。

一个日益流行的解决方案是使用复制构造函数和交换方法实现operator=

1
2
3
4
5
6
MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    swap(tmp);
    return *this;
}

甚至:

1
2
3
4
5
MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}

swap函数通常很容易编写,因为它只是交换内部的所有权,不必清理现有状态或分配新资源。

复制和交换习惯用法的优点是,它是自动自分配安全的,并且——只要交换操作是不抛出的——也是非常安全的异常。

为确保异常安全,"手写"的分配运算符通常必须在取消分配受让人的旧资源之前分配新资源的副本,以便在分配新资源时发生异常时,仍然可以返回旧状态。所有这些在复制和交换中都是免费的,但通常更复杂,因此容易出错,从头开始。

要小心的一件事是确保swap方法是真正的swap,而不是使用复制构造函数和赋值操作符本身的默认std::swap

通常使用会员制的swapstd::swap可以工作,并且对所有基本类型和指针类型都有"不抛出"保证。大多数智能指针也可以交换为不抛出保证。


复制构造函数对以前是原始内存的对象执行首次初始化。赋值运算符Otoh用新值覆盖现有值。通常情况下,这涉及到废弃旧资源(例如内存)和分配新资源。

如果两者之间存在相似性,则表示赋值运算符执行销毁和复制构造。一些开发人员过去常常通过就地销毁和放置副本构造来实际实现分配。然而,这是一个非常坏的主意。(如果这是在派生类赋值期间调用的基类的赋值运算符,该怎么办?)

现在人们通常认为的标准成语是使用swap,正如查尔斯所建议的:

1
2
3
4
5
MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}

它使用复制构造(请注意,other是复制的)和销毁(在函数结束时被销毁)并按正确的顺序使用它们:销毁前的构造(可能会失败)(不能失败)。


我有点担心:

1
2
3
4
5
6
MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    swap(tmp);
    return *this;
}

首先,当我的大脑在思考"复制"时,阅读"交换"一词会刺激我的常识。另外,我对这个花式把戏的目的表示怀疑。是的,在构建新的(复制的)资源时,任何异常都应该发生在交换之前,这似乎是一种安全的方法,可以确保在使所有新数据生效之前都已填充。

那很好。那么,在交换之后会发生什么异常呢?(当临时对象超出范围时,旧资源被破坏)从分配的用户的角度来看,操作失败了,但没有失败。它有一个巨大的副作用:复制确实发生了。只是一些资源清理失败了。目标对象的状态已更改,即使操作似乎从外部失败。

因此,我建议不要用"交换"来进行更自然的"转移":

1
2
3
4
5
6
MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    transfer(tmp);
    return *this;
}

还有临时对象的构造,但下一步立即行动是在将源的资源移到目标之前释放目标的所有当前资源(并使其无效,这样它们就不会被双重释放)。

我建议不要构造、移动、销毁,而是构造、销毁、移动。这是最危险的行动,是在其他一切都解决之后最后采取的行动。

是的,破坏失败是两种方案中的一个问题。数据要么已损坏(在您认为不存在时复制),要么已丢失(在您认为不存在时释放)。失败者胜于败者。没有比坏数据更好的数据。

转移而不是交换。这是我的建议。