复制和交换习惯用法在C ++ 11中仍然有用吗 [英] Is the copy and swap idiom still useful in C++11

查看:96
本文介绍了复制和交换习惯用法在C ++ 11中仍然有用吗的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我指的是这个问题:
什么是副本-

有效地,以上答案导致了以下实现:

Effectively, the above answer leads to the following implementation:

class MyClass
{
public:
    friend void swap(MyClass & lhs, MyClass & rhs) noexcept;

    MyClass() { /* to implement */ };
    virtual ~MyClass() { /* to implement */ };
    MyClass(const MyClass & rhs) { /* to implement */ }
    MyClass(MyClass && rhs) : MyClass() { swap(*this, rhs); }
    MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }
};

void swap( MyClass & lhs, MyClass & rhs )
{
    using std::swap;
    /* to implement */
    //swap(rhs.x, lhs.x);
}

但是,请注意,我们可以完全避开swap(),执行以下操作:

However, notice that we could eschew the swap() altogether, doing the following:

class MyClass
{
public:
    MyClass() { /* to implement */ };
    virtual ~MyClass() { /* to implement */ };
    MyClass(const MyClass & rhs) { /* to implement */ }
    MyClass(MyClass && rhs) : MyClass() { *this = std::forward<MyClass>(rhs);   }
    MyClass & operator=(MyClass rhs)
    { 
        /* put swap code here */ 
        using std::swap;
        /* to implement */
        //swap(rhs.x, lhs.x);
        // :::
        return *this;
    }
};

请注意,这意味着我们将不再在std :: swap上进行有效的依赖于参数的查找MyClass。

Note that this means that we will no longer have a valid argument dependent lookup on std::swap with MyClass.

总而言之,使用swap()方法有什么好处。

In short is there any advantage of having the swap() method.

编辑:

我意识到上面第二个实现中存在一个可怕的错误,这是一个很大的事情,所以我将其保留为-

I realized there is a terrible mistake in the second implementation above, and its quite a big thing so I will leave it as-is to instruct anybody who comes across this.

如果运算符=被定义为

MyClass2 & operator=(MyClass2 rhs)

然后,无论何时rhs是r值,都会调用move构造函数。但是,这意味着在使用时:

Then whenever rhs is a r-value, the move constructor will be called. However, this means that when using:

MyClass2(MyClass2 && rhs)
{
    //*this = std::move(rhs);
}

请注意,您最终以递归方式调用move构造函数,因为operator =调用move构造函数...

Notice you end up with a recursive call to the move constructor, as operator= calls the move constructor...

在运行时堆栈溢出之前,这是很难发现的。

This is very subtle and hard to spot until you get a runtime stack overflow.

现在解决的办法是同时拥有这两个

Now the fix to that would be to have both

MyClass2 & operator=(const MyClass2 &rhs)
MyClass2 & operator=(MyClass2 && rhs)

这使我们可以将副本构造函数定义为

this allows us to define the copy constructors as

MyClass2(const MyClass2 & rhs)
{
    operator=( rhs );
}

MyClass2(MyClass2 && rhs)
{
    operator=( std::move(rhs) );
}

请注意,您编写了相同数量的代码,复制构造函数 -free,您只需编写operator =(&)而不是副本构造函数,并编写operator =(&&)而不是swap()方法。

Notice that you write the same amount of code, the copy constructors come "for-free" and you just write operator=(&) instead of the copy constructor and operator=(&&) instead of the swap() method.

推荐答案

首先,您还是做错了。复制和交换惯用语在那里可以将构造函数重用于赋值运算符(而不是相反),得益于已经正确构造了构造函数代码,并保证了赋值运算符的强大异常安全性。但您不要在move构造函数中调用swap。复制构造器以相同的方式复制所有数据(无论在单个类的给定上下文中意味着什么),移动构造器移动该数据,移动构造器构造并分配/交换:

First of all, you're doing it wrong anyway. The copy-and-swap idiom is there to reuse the constructor for the assignment operator (and not the other way around), profiting from already properly constructing constructor code and guaranteeing strong exception safety for the assignment operator. But you don't call swap in the move constructor. In the same way the copy constructor copies all data (whatever that means in the given context of an individual class), the move constructor moves this data, your move constructor constructs and assigns/swaps:

MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }

在您的替代版本中,这就是

And this would in your alternative version just be

MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { using std::swap; swap(x, rhs.x); return *this; }

在调用构造函数内部调用赋值运算符时不会出现严重错误。您永远不要调用赋值运算符或在构造函数内交换整个对象。构造函数在那里照顾施工,并且具有不必担心破坏先前数据的优点,因为该数据尚不存在。构造函数同样可以处理不可默认构造的类型,但最后但不是的类型最常见的是,直接构造比进行赋值/交换的默认构造更有效。

Which doesn't exhibit the severe error introduced by calling the assignment operator inside the constructor. You should never ever call the assignment operator or swap the whole object inside a constructor. Constructors are there to care for construction and have the advantage of not having to care for the, well, destruction of the previous data, since that data doesn't exist yet. And likewise can constructors handle types not default constructible and last but not least often direct construction can be more performant than defualt construction followed by assignment/swap.

但是要回答您的问题,这整个过程仍然是复制和交换的习惯用法,只是没有显式的 swap 函数。在C ++ 11中,它甚至更有用,因为现在您已经使用单个函数实现了复制移动赋值。

But to answer your question, this whole thing is still the copy-and-swap idiom, just without an explicit swap function. And in C++11 it is even more useful because now you have implemented both copy and move assignment with a single function.

如果交换函数在赋值运算符之外仍然是有价值的,这是一个完全不同的问题,并且取决于这种类型是否有可能被交换。实际上,在C ++ 11中,具有适当移动语义的类型可以使用默认的 std :: swap 实现足够有效地进行交换,从而通常无需进行其他自定义交换。只是要确保不要在赋值运算符内部调用此默认 std :: swap ,因为它本身会执行移动赋值(这将导致与错误实现相同的问题

If the swap function is still of value outside of the assignment operator is an entirely different question and depends if this type is likely to be swapped, anyway. In fact in C++11 types with proper move semantics can just be swapped sufficiently efficient using the default std::swap implementation, often eliminating the need for an additional custom swap. Just be sure not to call this default std::swap inside of your assignment operator, since it does a move assignment itself (which would lead to the same problems as your wrong implementation of the move constructor).

但是要说一遍,是否自定义 swap 函数,这不是更改复制和交换习惯用法的用途,这在C ++ 11中更加有用,从而无需实现其他功能。

But to say it again, custom swap function or not, this doesn't change anything in the usefulness of the copy-and-swap idiom, which is even more useful in C++11, eliminating the need to implement an additional function.

这篇关于复制和交换习惯用法在C ++ 11中仍然有用吗的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆