C++ 复制和交换习惯用法是否应该成为C+中的复制和移动习惯用法+;11?

C++ 复制和交换习惯用法是否应该成为C+中的复制和移动习惯用法+;11?,c++,c++11,move-semantics,assignment-operator,copy-and-swap,C++,C++11,Move Semantics,Assignment Operator,Copy And Swap,如中所述,复制和交换习惯用法的实现方式如下: class MyClass { private: BigClass data; UnmovableClass *dataPtr; public: MyClass() : data(), dataPtr(new UnmovableClass) { } MyClass(const MyClass& other) : data(other.data), dataPtr(new Unmovab

如中所述,复制和交换习惯用法的实现方式如下:

class MyClass
{
private:
    BigClass data;
    UnmovableClass *dataPtr;

public:
    MyClass()
      : data(), dataPtr(new UnmovableClass) { }
    MyClass(const MyClass& other)
      : data(other.data), dataPtr(new UnmovableClass(*other.dataPtr)) { }
    MyClass(MyClass&& other)
      : data(std::move(other.data)), dataPtr(other.dataPtr)
    { other.dataPtr= nullptr; }

    ~MyClass() { delete dataPtr; }

    friend void swap(MyClass& first, MyClass& second)
    {
        using std::swap;
        swap(first.data, other.data);
        swap(first.dataPtr, other.dataPtr);
    }

    MyClass& operator=(MyClass other)
    {
        swap(*this, other);
        return *this;
    }
};
通过将MyClass的值作为operator=的参数,该参数可以由复制构造函数或移动构造函数构造。然后可以安全地从参数中提取数据。这可以防止代码重复,并有助于异常安全

答案提到您可以交换或移动临时表中的变量。它主要讨论交换。但是,如果编译器没有对交换进行优化,则交换涉及三个移动操作,在更复杂的情况下,交换会做额外的工作。当您只需要将临时对象移动到“指定给”对象中时

考虑这个更复杂的例子,涉及到。在本例中,我手动编写了赋值运算符代码。重点介绍移动构造函数、赋值运算符和交换方法:

class MyClass : Observable::IObserver
{
private:
    std::shared_ptr<Observable> observable;

public:
    MyClass(std::shared_ptr<Observable> observable) : observable(observable){ observable->registerObserver(*this); }
    MyClass(const MyClass& other) : observable(other.observable) { observable.registerObserver(*this); }
    ~MyClass() { if(observable != nullptr) { observable->unregisterObserver(*this); }}

    MyClass(MyClass&& other) : observable(std::move(other.observable))
    {
        observable->unregisterObserver(other);
        other.observable.reset(nullptr);
        observable->registerObserver(*this);
    }

    friend void swap(MyClass& first, MyClass& second)
    {
        //Checks for nullptr and same observable omitted
            using std::swap;
            swap(first.observable, second.observable);

            second.observable->unregisterObserver(first);
            first.observable->registerObserver(first);
            first.observable->unregisterObserver(second);
            second.observable->registerObserver(second);
    }

    MyClass& operator=(MyClass other)
    {
        observable->unregisterObserver(*this);
        observable = std::move(other.observable);

        observable->unregisterObserver(other);
        other.observable.reset(nullptr);
        observable->registerObserver(*this);
    }
}

在我看来,这种方法从来都不亚于交换方法。我是否正确地认为复制和交换习惯用法比C++11中的复制和移动习惯用法更好,还是我遗漏了一些重要的东西?

首先,只要类是可移动的,通常不需要在C++11中编写
交换
函数。默认的
交换
将采用移动:

void swap(T& left, T& right) {
    T tmp(std::move(left));
    left = std::move(right);
    right = std::move(tmp);
}
就这样,元素交换了

其次,基于此,复制和交换实际上仍然有效:

T& T::operator=(T const& left) {
    using std::swap;
    T tmp(left);
    swap(*this, tmp);
    return *this;
}

// Let's not forget the move-assignment operator to power down the swap.
T& T::operator=(T&&) = default;
将复制和交换(移动)或移动和交换(移动),并且应始终实现接近最佳性能。可能会有一些多余的赋值,但希望您的编译器会处理好


编辑:这仅实现复制分配操作符;还需要一个单独的移动分配操作符,尽管它可以默认,否则会发生堆栈溢出(移动分配和交换会无限期地相互调用)。

给予每个特殊成员应有的关爱,并尽可能地默认他们:

class MyClass
{
private:
    BigClass data;
    std::unique_ptr<UnmovableClass> dataPtr;

public:
    MyClass() = default;
    ~MyClass() = default;
    MyClass(const MyClass& other)
        : data(other.data)
        , dataPtr(other.dataPtr ? new UnmovableClass(*other.dataPtr)
                                : nullptr)
        { }
    MyClass& operator=(const MyClass& other)
    {
        if (this != &other)
        {
            data = other.data;
            dataPtr.reset(other.dataPtr ? new UnmovableClass(*other.dataPtr)
                                        : nullptr);
        }
        return *this;
    }
    MyClass(MyClass&&) = default;
    MyClass& operator=(MyClass&&) = default;

    friend void swap(MyClass& first, MyClass& second)
    {
        using std::swap;
        swap(first.data, second.data);
        swap(first.dataPtr, second.dataPtr);
    }
};
class-MyClass
{
私人:
大类数据;
std::unique_ptr dataPtr;
公众:
MyClass()=默认值;
~MyClass()=默认值;
MyClass(常数MyClass和其他)
:数据(其他.数据)
,dataPtr(other.dataPtr?新的不可移动类(*other.dataPtr)
:nullptr)
{ }
MyClass和运算符=(常量MyClass和其他)
{
如果(此!=&其他)
{
数据=其他数据;
dataPtr.reset(other.dataPtr?新的不可移动类(*other.dataPtr)
:nullptr);
}
归还*这个;
}
MyClass(MyClass&&)=默认值;
MyClass&operator=(MyClass&&)=默认值;
朋友无效交换(MyClass&first,MyClass&second)
{
使用std::swap;
交换(第一个数据,第二个数据);
交换(第一个.dataPtr,第二个.dataPtr);
}
};
如果需要,可以在上面隐式默认析构函数。对于本例,其他所有内容都需要显式定义或默认设置

参考:

复制/交换习惯用法可能会降低性能(参见幻灯片)。例如,有没有想过为什么像
std::vector
std::string
这样的高性能/常用std::类型不使用复制/交换?表现不佳是原因。如果
BigClass
包含任何
std::vector
s或
std::string
s(看起来很可能),那么最好从特殊成员中调用他们的特殊成员。以上是如何做到这一点


如果您在作业中需要强大的异常安全性,请参阅幻灯片,了解如何在性能之外提供这种安全性(搜索“strong_assign”)。

我问这个问题已经很久了,我知道答案已经有一段时间了,但我已经推迟了编写答案。给你

答案是否定的。复制和交换习惯用法不应该成为复制和移动习惯用法

复制和交换(也是移动构造和交换)的一个重要部分是通过安全清理实现赋值运算符的方法。将旧数据交换为构建的副本或临时构建的移动。操作完成后,将删除临时对象,并调用其析构函数

交换行为是为了能够重用析构函数,因此您不必在赋值运算符中编写任何清理代码

如果没有要执行的清理行为,只有赋值,那么您应该能够将赋值运算符声明为默认值,并且不需要复制和交换

移动构造函数本身通常不需要任何清理行为,因为它是一个新对象。一般的简单方法是让move构造函数调用默认构造函数,然后用move-from对象交换所有成员。然后,“从中移动”对象将类似于一个平淡的默认构造对象


然而,在这个问题的观察者模式示例中,这实际上是一个例外,您必须进行额外的清理工作,因为需要更改对旧对象的引用。一般来说,我建议尽可能使您的观察者和可观察者以及基于引用的其他设计构造不可移动。

应该注意的是,如果您自己不专门化,则
std::swap
的默认实现将使用移动构造函数,生成的代码与第二个示例相当。@ComicSanms它将使用一个移动构造和两个移动赋值。对于移动构造函数,移动代码已经存在,因此似乎不需要使用交换来代替重用移动构造函数代码。@Aberrant:对编译器有点信心,这个问题非常冗长,可能用不到一半的篇幅来表达。你能清理一下吗?@mydogis
class MyClass
{
private:
    BigClass data;
    std::unique_ptr<UnmovableClass> dataPtr;

public:
    MyClass() = default;
    ~MyClass() = default;
    MyClass(const MyClass& other)
        : data(other.data)
        , dataPtr(other.dataPtr ? new UnmovableClass(*other.dataPtr)
                                : nullptr)
        { }
    MyClass& operator=(const MyClass& other)
    {
        if (this != &other)
        {
            data = other.data;
            dataPtr.reset(other.dataPtr ? new UnmovableClass(*other.dataPtr)
                                        : nullptr);
        }
        return *this;
    }
    MyClass(MyClass&&) = default;
    MyClass& operator=(MyClass&&) = default;

    friend void swap(MyClass& first, MyClass& second)
    {
        using std::swap;
        swap(first.data, second.data);
        swap(first.dataPtr, second.dataPtr);
    }
};