在 C++11 中,Copy-and-Swap 习语应该变成 Copy-and-Move 习语吗? [英] Should the Copy-and-Swap Idiom become the Copy-and-Move Idiom in C++11?

查看:21
本文介绍了在 C++11 中,Copy-and-Swap 习语应该变成 Copy-and-Move 习语吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此答案中所述,复制和交换习语的实现方式如下:

As explained in this answer, the copy-and-swap idiom is implemented as follows:

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= 的参数,该参数可以由复制构造函数或移动构造函数构造.然后,您可以安全地从参数中提取数据.这可以防止代码重复并有助于异常安全.

By having a value of MyClass as parameter for operator=, the parameter can be constructed by either the copy constructor or the move constructor. You can then safely extract the data from the parameter. This prevents code duplication and assists in exception safety.

答案提到您可以交换或移动临时变量.它主要讨论交换.但是,如果没有经过编译器优化,交换会涉及三个移动操作,并且在更复杂的情况下会执行额外的额外工作.如果您只想将临时对象移动到分配给的对象中.

The answer mentions you can either swap or move the variables in the temporary. It primarily discusses swapping. However, a swap, if not optimised by the compiler, involves three move operations, and in more complex cases does additional extra work. When all you want, is to move the temporary into the assigned-to object.

考虑这个更复杂的例子,涉及观察者模式.在本例中,我手动编写了赋值运算符代码.重点是移动构造函数、赋值运算符和交换方法:

Consider this more complex example, involving the observer pattern. In this example, I've written the assignment operator code manually. Emphasis is on the move constructor, assignment operator and swap method:

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

显然,这个手动编写的赋值运算符中的代码重复部分与移动构造函数的代码相同.您可以在赋值运算符中执行交换并且行为是正确的,但它可能会执行更多移动并执行额外的注册(在交换中)和取消注册(在析构函数中).

Clearly, the duplicated part of the code in this manually written assignment operator is identical to that of the move constructor. You could perform a swap in the assignment operator and the behaviour would be right, but it would potentially perform more moves and perform an extra registration (in the swap) and unregistration (in the destructor).

重用移动构造函数的代码不是更有意义吗?

Wouldn't it make much more sense to reuse the move constructor's code in stead?

private:
    void performMoveActions(MyClass&& other)
    {
        observable->unregisterObserver(other);
        other.observable.reset(nullptr);
        observable->registerObserver(*this);
    }

public:
    MyClass(MyClass&& other) : observable(std::move(other.observable))
    {
        performMoveActions(other);
    }

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

        performMoveActions(other);
    }

在我看来,这种方法绝不逊色于交换方法.我认为复制和交换习语会比 C++11 中的复制和移动习语更好,还是我错过了一些重要的东西?

It looks to me like this approach is never inferior to the swap approach. Am I right in thinking that the copy-and-swap idiom would be better off as the copy-and-move idiom in C++11, or did I miss something important?

推荐答案

好久没问这个问题了,现在我已经知道答案有一段时间了,但我推迟了为它.在这里.

It's been a long time since I asked this question, and I've known the answer for a while now, but I've put off writing the answer for it. Here it is.

答案是否定的.Copy-and-swap 成语不应该变成 Copy-and-move 成语.

The answer is no. The Copy-and-swap idiom should not become the Copy-and-move idiom.

Copy-and-swap(也是Move-construct-and-swap)的一个重要部分是一种通过安全清理实现赋值运算符的方法.旧数据被交换到复制构造或移动构造的临时数据中.操作完成后,临时文件被删除,并调用其析构函数.

An important part of Copy-and-swap (which is also Move-construct-and-swap) is a way to implement assignment operators with safe cleanup. The old data is swapped into a copy-constructed or move-constructed temporary. When the operation is done, the temporary is deleted, and its destructor is called.

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

The swap behaviour is there to be able to reuse the destructor, so you don't have to write any cleanup code in your assignment operators.

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

If there's no cleanup behaviour to be done and only assignment, then you should be able to declare the assignment operators as default and copy-and-swap isn't needed.

移动构造函数本身通常不需要任何清理行为,因为它是一个新对象.一般简单的方法是让移动构造函数调用默认构造函数,然后将所有成员与移动自对象交换.移出的对象将像一个平淡的默认构造对象.

The move constructor itself usually doesn't require any clean-up behaviour, since it's a new object. The general simple approach is to make the move constructor invoke the default constructor, and then swap all the members with the move-from object. The moved-from object will then be like a bland default-constructed object.

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

However, in this question's observer pattern example, that's actually an exception where you have to do extra cleanup work because references to the old object need to be changed. In general, I would recommend making your observers and observables, and other design constructs based around references, unmovable whenever possible.

这篇关于在 C++11 中,Copy-and-Swap 习语应该变成 Copy-and-Move 习语吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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