C++ 中的复制构造函数和 = 运算符重载:是否可以使用通用函数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1734628/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 21:03:12  来源:igfitidea点击:

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

c++variable-assignmentcopy-constructorc++-faq

提问by MPelletier

Since a copy constructor

由于复制构造函数

MyClass(const MyClass&);

and an = operator overload

和 = 运算符重载

MyClass& operator = (const MyClass&);

have pretty much the same code, the same parameter, and only differ on the return, is it possible to have a common function for them both to use?

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

回答by CB Bailey

Yes. There are two common options. One - which is generally discouraged - is to call the operator=from the copy constructor explicitly:

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

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

However, providing a good operator=is a challenge when it comes to dealing with the old state and issues arising from self assignment. Also, all members and bases get default initialized first even if they are to be assigned to from other. This may not even be valid for all members and bases and even where it is valid it is semantically redundant and may be practically expensive.

但是,operator=在处理旧状态和自我分配引起的问题时,提供商品是一项挑战。此外,即使将所有成员和基数分配给 from ,也会首先对其进行默认初始化other。这甚至可能不适用于所有成员和基础,即使它有效,它在语义上也是多余的,而且实际上可能很昂贵。

An increasingly popular solution is to implement operator=using the copy constructor and a swap method.

越来越流行的解决方案是operator=使用复制构造函数和交换方法来实现。

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

or even:

甚至:

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

A swapfunction is typically simple to write as it just swaps the ownership of the internals and doesn't have to clean up existing state or allocate new resources.

一个swap函数通常是简单的写,因为它只是交换了内部的所有权,并没有清理现有状态或分配新的资源。

Advantages of the copy and swap idiom is that it is automatically self-assignment safe and - providing that the swap operation is no-throw - is also strongly exception safe.

复制和交换习语的优点是它是自动自赋值安全的,并且——假设交换操作是不抛出的——也是强异常安全的。

To be strongly exception safe, a 'hand' written assignment operator typically has to allocate a copy of the new resources before de-allocating the assignee's old resources so that if an exception occurs allocating the new resources, the old state can still be returned to. All this comes for free with copy-and-swap but is typically more complex, and hence error prone, to do from scratch.

为了高度异常安全,“手写”赋值运算符通常必须在解除分配受让人的旧资源之前分配新资源的副本,以便如果在分配新资源时发生异常,旧状态仍然可以返回到. 所有这些都是通过复制和交换免费提供的,但通常更复杂,因此容易出错,从头开始。

The one thing to be careful of is to make sure that the swap method is a true swap, and not the default std::swapwhich uses the copy constructor and assignment operator itself.

需要注意的一件事是确保交换方法是真正的交换,而不是std::swap使用复制构造函数和赋值运算符本身的默认方法。

Typically a memberwise swapis used. std::swapworks and is 'no-throw' guaranteed with all basic types and pointer types. Most smart pointers can also be swapped with a no-throw guarantee.

通常使用成员swap方式。std::swap有效并且对所有基本类型和指针类型保证“不抛出”。大多数智能指针也可以与无抛出保证交换。

回答by sbi

The copy constructor performs first-time initialization of objects that used to be raw memory. The assignment operator, OTOH, overrides existing values with new ones. More often than never, this involves dismissing old resources (for example, memory) and allocating new ones.

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

If there's a similarity between the two, it's that the assignment operator performs destruction and copy-construction. Some developers used to actually implement assignment by in-place destruction followed by placement copy-construction. However, this is a verybad idea. (What if this is the assignment operator of a base class that called during assignment of a derived class?)

如果两者之间存在相似之处,那就是赋值运算符执行销毁和复制构造。一些开发人员过去常常通过就地销毁然后放置复制构造来实际实现分配。然而,这是一个非常糟糕的主意。(如果这是在派生类赋值期间调用的基类的赋值运算符呢?)

What's usually considered the canonical idiom nowadays is using swapas Charles suggested:

现在通常被认为是规范的成语,swap正如查尔斯所建议的那样:

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

This uses copy-construction (note that otheris copied) and destruction (it's destructed at the end of the function) -- and it uses them in the right order, too: construction (might fail) before destruction (must not fail).

这使用了复制构造(注意other被复制)和销毁(它在函数结束时被销毁)——并且它也以正确的顺序使用它们:在销毁(不能失败)之前构造(可能失败)。

回答by Matthew

Something bothers me about:

有件事困扰着我:

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

First, reading the word "swap" when my mind is thinking "copy" irritates my common sense. Also, I question the goal of this fancy trick. Yes, any exceptions in constructing the new (copied) resources should happen before the swap, which seems like a safe way to make sure all the new data is filled before making it go live.

首先,当我想到“复制”时读到“交换”这个词会刺激我的常识。另外,我质疑这个花哨的把戏的目标。是的,构建新(复制)资源的任何异常都应该在交换之前发生,这似乎是一种确保所有新数据在上线之前都已填充的安全方法。

That's fine. So, what about exceptions that happen after the swap? (when the old resources are destructed when the temporary object goes out of scope) From the perspective of the user of the assignment, the operation has failed, except it didn't. It has a huge side effect: the copy did actually happen. It was only some resource cleanup that failed. The state of the destination object has been altered even though the operation seems from the outside to have failed.

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

So, I propose instead of "swap" to do a more natural "transfer":

所以,我建议不要“交换”来做一个更自然的“转移”:

MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    transfer(tmp);
    return *this;
}

There's still the construction of the temporary object, but the next immediate action is to free all current resources of the destination before moving (and NULLing so they won't be double-freed) the resources of the source to it.

仍然有临时对象的构造,但下一个直接操作是在将源的资源移动(并为 NULL 以便它们不会被双重释放)之前释放目标的所有当前资源。

Instead of { construct, move, destruct }, I propose { construct, destruct, move }. The move, which is the most dangerous action, is the one taken last after everything else has been settled.

我建议使用 {construct, destruct, move },而不是 {construct, move, destruct }。这一举动是最危险的行动,是在其他一切都解决后最后采取的行动。

Yes, destruction fail is a problem in either scheme. The data is either corrupted (copied when you didn't think it was) or lost (freed when you didn't think it was). Lost is better than corrupted. No data is better than bad data.

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

Transfer instead of swap. That's my suggestion anyway.

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