繁体   English   中英

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

[英]Is the copy and swap idiom still useful in 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);
}

但是,请注意我们可以完全避开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;
    }
};

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

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


编辑:

我意识到在上面的第二个实现中存在一个可怕的错误,这是一个非常重要的事情,所以我会把它留给原来指示任何碰到这个的人。

if operator =定义为

MyClass2 & operator=(MyClass2 rhs)

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

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

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

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

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

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

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

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

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

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

首先,无论如何,你做错了。 复制和交换习惯用于重用赋值运算符的构造函数(而不是相反),从已经正确构造构造函数代码中获益并保证赋值运算符的强异常安全性。 但是你不要在移动构造函数中调用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; }

这将是你的替代版本

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; }

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

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

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

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

你当然不会考虑整体情况。 您的代码重用了不同赋值运算符的构造函数,原始的重用运算符用于不同的构造函数。 这基本上是一回事,你所做的就是改变它。

除了因为它们编写构造函数之外,它们可以处理非默认构造类型或类型,如果没有像int那样显式初始化,那么它们的值是坏的,或者对于default-construct来说是非常昂贵的,或者默认构造的成员无法破坏(例如,考虑一个智能指针 - 未初始化的T *导致错误的删除)。

所以基本上,你所取得的成就是相同的原则,但在一个明显更糟糕的地方。 哦,你必须定义所有四个函数,否则相互递归,而原始的copy-and-swap只定义了三个函数。

使用复制和交换习惯用法实现拷贝分配的原因(如果有的话)的有效性在C ++ 11中与在以前的版本中是相同的。

另请注意,您应该在移动构造函数中使用std::move on成员变量,并且应该对任何作为函数参数的rvalue引用使用std::move

std::forward应仅用于T&&形式的模板参数引用以及auto&&变量(在类型推导期间可以将其引用折叠为左值引用)以保持它们的rvalueness或lvalueness。

对于最好的,在C ++ 11中这样做:

class MyClass {
public:
    MyClass(MyString&& pStr) : str(pStr)) { 
        // call MyString(MyString&& pStr)
    }
    MyClass(const MyString& pStr) : str(pStr)) { 
        // call MyString(const MyString& pStr)
    }
private:
    MyString const str; // Still const!
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM