首页 > 解决方案 > 隐式移动与复制操作和遏制

问题描述

当一个类有一个未定义其移动操作的成员时,我很难理解隐式移动操作:

int main() {
    struct A // no move: move = copy
    {
        A() = default;
        A(const A&) {
            cout << "A'copy-ctor\n";
        };
        A& operator=(const A&) {
            cout << "A'copy-assign\n";
            return *this;
        }
    };

    struct B
    {
        B() = default;
        A a; // does this make B non-moveable?
        unique_ptr<int> upi;
        // B(B&&) noexcept = default;
        // B& operator=(B&&)noexcept = default;
    };

    A a;
    A a2 = std::move(a); // ok use copy ctor instead of move one
    a2 = std::move(a); // ok use copy assignment instead of move one

    B b;
    B b2 = std::move(b); // why this works?
    b = std::move(b2); // and this works?
    // b = b2; // error: copy deleted because of non-copyable member upi

    cout << "\nDone!\n";
}

所以我看到的A是一个不可移动的类,因为它定义了复制控制操作,所以它只能被复制,并且任何试图移动这个类的对象,都会使用相应的复制操作。

如果我是正确的,直到这里它是好的。但是B有一个不可复制的对象upiunique_ptr因此复制操作被定义为已删除的函数,因此我们无法复制此类的对象。但是这个类有一个不可移动的对象a,因此我认为这个类 ( B) 既不能复制也不能移动。但是为什么初始化b2和分配b工作正常?究竟会发生什么?

B b2 = std::move(b); // ok?!

为什么上面的行调用了类的复制构造函数A并且它调用了移动构造函数B

谁能帮我到底发生了什么?在在这里发布问题之前,我已经在 cppreference 和许多网站上搜索并阅读过。

输出:

A'copy-ctor
A'copy-assign
A'copy-ctor
A'copy-assign

Done!

标签: c++c++11copy-constructormove-constructorimplicit-methods

解决方案


请记住在 C++ 中“移动”数据的含义(假设我们遵循通常的约定)。如果您将 object 移动x到 object y,则y接收所有之前的数据x并且x是……好吧,我们不在乎是什么x,只要它仍然对销毁有效。通常我们认为x丢失了所有数据,但这不是必需的。所需要的x就是有效的。如果x最终得到与 相同的数据y,我们不在乎。

复制xy导致y接收 中的所有数据x,并x保持有效状态(假设复制操作遵循约定并且没有错误)。因此,复制算作移动。除了复制操作之外还定义移动操作的原因不是为了允许新的东西,而是为了在某些情况下允许更高的效率。除非您采取措施防止移动,否则可以移动任何可以复制的东西。

所以我看到的A是一个不可移动的类,因为它定义了复制控制操作,所以它只能被复制,任何试图移动这个类的对象,都会使用相应的复制操作。

我看到的A是一个可移动的类(尽管缺少移动构造函数和移动赋值),因为它定义了它的复制控制操作。任何移动此类对象的尝试都将依赖于相应的复制操作。如果您希望类可复制但不可移动,则需要删除移动操作,同时保留复制操作。(试试看。添加A(A&&) = delete;到您的定义中A。)

该类B有一个可以移动或复制的成员,以及一个可以移动但不能复制的成员。所以B它本身可以移动但不能复制。当B被移动时,unique_ptr成员将按照您的预期移动,并且A成员将被复制(移动类型对象的后备A)。


对我来说情况变得更糟:如果我取消注释中的移动操作行,B上面的初始化将不会编译抱怨引用已删除的函数,赋值也是如此!

更仔细地阅读错误消息。当我复制这个结果时,“使用已删除函数”错误后面跟着一条提供更多详细信息的注释:移动构造函数被删除,因为“它的异常规范与隐式异常规范不匹配”。删除noexcept关键字允许代码编译(使用 gcc 9.2 和 6.1)。

或者,您可以添加noexcept到复制构造函数和复制赋值A(保持noexcept的移动操作B)。这是演示默认移动操作B使用 的复制操作的一种方式A


推荐阅读