隐式移动与复制操作和包含

Implicit move vs copy operations and containment

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

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 是不可移动的 class 因为它的复制控制操作的定义所以它只能被复制并且任何试图移动这个对象的 class =40=],用对应的复制操作代替。

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

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

为什么上面那行代码调用了classA的拷贝构造函数而调用了B的移动构造函数?

谁能帮我看看究竟发生了什么?在此处发布问题之前,我已经在 cppreference 和许多网站上进行了搜索和阅读。

输出:

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

Done!

std::move 不强制复制对象。它只是 returns &&-reference (它允许编译器使用 move ctor/assign 运算符)。
在复制 1,2 对象的情况下。
在 3,4 个案例中(我认为)对象被移动。但是 A 仍然被复制,因为它不能被移动。

请记住 C++ 中 "move" 数据的含义(假设我们遵循通常的约定)。如果将对象 x 移动到对象 y,那么 y 会接收到 x 中的所有数据,而 x 是......好吧,我们不不关心x是什么,只要它对销毁仍然有效即可。我们通常认为 x 丢失了所有数据,但这不是必需的。所需要的只是 x 有效。如果 x 最终得到与 y 相同的数据,我们不在乎。

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

So what I see is A is a non-moveable class because of the definition of its copy control operations so it can only be copied and any attempt to move an object of this class, the corresponding copy operation is used instead.

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

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


Things get more worse for me: if I uncomment the lines of move operations in B, the initialization above will not compile complaining about referencing a deleted funtion, the same thing for the assignment!

仔细阅读错误消息。当我复制此结果时,"use of deleted function" 错误后跟一个提供更多详细信息的注释:移动构造函数已删除,因为 "its exception-specification does not match the implicit exception-specification"。删除 noexcept 关键字允许代码编译(使用 gcc 9.2 和 6.1)。

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

的复制操作的一种方法

这里是@JaMiT 的优秀回答的总结:

Class A is moveable via it's copy-constructor and copy-assignment operator, even though class A is not MoveConstructible and is not MoveAssignable. See the notes on cppreference.com's pages for MoveConstructible and MoveAssignable.

And thus class B is also moveable.

The language allows you to prevent moveability for class A by explicitly =delete'ing the move-constructor and move-assignment, even though class A is still copyable.

是否有一个可复制但不可移动的实际原因class?几年前有人问过这个问题 here。答案和评论很难找到任何实际理由想要一个可复制但不可移动的 class.