复制和交换习惯用法在C++ 11中是否仍然有用

aCu*_*ria 16 c++ swap c++11

我提到这个问题: 什么是复制和交换习惯用法?

实际上,上述答案导致以下实施:

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

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

简而言之,使用swap()方法有任何好处.


编辑:

我意识到在上面的第二个实现中存在一个可怕的错误,这是一个非常重要的事情,所以我将其留下来指示任何遇到此问题的人.

if operator =定义为

MyClass2 & operator=(MyClass2 rhs)
Run Code Online (Sandbox Code Playgroud)

然后,只要rhs是r值,就会调用移动构造函数.但是,这意味着在使用时:

MyClass2(MyClass2 && rhs)
{
    //*this = std::move(rhs);
}
Run Code Online (Sandbox Code Playgroud)

请注意,最终会对move构造函数进行递归调用,因为operator =调用move构造函数...

这是非常微妙的,很难发现,直到你得到运行时堆栈溢出.

现在解决这个问题就是两者都有

MyClass2 & operator=(const MyClass2 &rhs)
MyClass2 & operator=(MyClass2 && rhs)
Run Code Online (Sandbox Code Playgroud)

这允许我们将副本构造函数定义为

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

MyClass2(MyClass2 && rhs)
{
    operator=( std::move(rhs) );
}
Run Code Online (Sandbox Code Playgroud)

请注意,您编写了相同数量的代码,复制构造函数"免费",您只需编写operator =(&)而不是copy构造函数和operator =(&&)而不是swap()方法.

Chr*_*ica 22

首先,无论如何,你做错了.复制和交换习惯用于重用赋值运算符的构造函数(而不是相反),从已经正确构造构造函数代码中获益并保证赋值运算符的强异常安全性.但是你不要在移动构造函数中调用swap.复制构造函数以相同的方式复制所有数据(无论在单个类的给定上下文中是什么意思),移动构造函数移动此数据,移动构造函数构造和分配/交换:

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

这将是你的替代版本

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; }
Run Code Online (Sandbox Code Playgroud)

它没有表现出在构造函数中调用赋值运算符引入的严重错误.您永远不应该调用赋值运算符或在构造函数中交换整个对象.构造者在那里关心构造,并且具有不必关心以前数据的破坏的优点,因为该数据尚不存在.同样,构造函数可以处理类型而不是默认构造,最后但并非最不重要的是,直接构造通常比defualt构造更高效,然后是赋值/交换.

但是要回答你的问题,整个事情仍然是复制和交换的习惯用语,只是没有明确的swap功能.在C++ 11中,它更有用,因为现在您已经使用单个函数实现了复制移动赋值.

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

但是再说一遍,自定义swap函数与否,这并没有改变复制和交换习惯用法的任何内容,这在C++ 11中更有用,无需实现附加功能.


Pup*_*ppy 2

你肯定没有考虑整个情况。您的代码对不同的赋值运算符重复使用构造函数,原始代码对不同的构造函数重复使用赋值运算符。这基本上是同一件事,你所做的只是改变它的位置。

除了因为他们编写构造函数之外,他们可以处理非默认构造的类型或如果没有显式初始化则其值很糟糕的类型,int或者默认构造的成本很昂贵,或者默认构造的成员对于析构无效(例如例如,考虑一个智能指针 - 未初始化的 T* 会导致错误的删除)。

所以基本上,你所取得的一切都是相同的原则,但在一个明显更糟糕的地方。哦,你必须定义所有四个函数,否则相互递归,而原始的复制和交换只定义了三个函数。