隐式移动与复制操作和遏制

Mae*_*tro 3 c++ copy-constructor move-constructor implicit-methods c++11

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

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

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

到这里为止,如果我是正确的就可以了。但是B有一个不可复制的对象upi,因此unique_ptr复制操作被定义为已删除的函数,因此我们无法复制此类的对象。但是这个类有一个不可移动的对象,a因此我认为这个类(B)既不可复制也不可移动。但为什么 的初始化b2和赋值b工作正常呢?到底发生了什么?

B b2 = std::move(b); // ok?!
Run Code Online (Sandbox Code Playgroud)

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

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

谁能帮我看看究竟发生了什么?在在这里发布问题之前,我已经用谷歌搜索并阅读了 cppreference 和许多网站。

输出:

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

JaM*_*MiT 5

请记住在 C++ 中“移动”数据意味着什么(假设我们遵循通常的约定)。如果将 object 移动x到 object y,然后接收所有曾经y存在的数据......好吧,我们不关心是什么,只要它仍然对销毁有效即可。我们通常认为会丢失所有数据,但这不是必需的。所需要的只是有效的。如果最终得到与 相同的数据,我们不在乎。xxxxxxy

复制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