琐碎的复制和移动操作是否不同?

Does trivial copying and moving operations differ?

让我们看看一些平凡的可移动构造和(不平凡的)可复制构造(但仍然可复制构造)用户定义(class)类型A

struct A
{
    A() = default;
    A(A const &) {}
    A(A &&) = default;
};

然后 A 的移动(移动构造或移动赋值)从字面上执行以下操作:源按位 复制 到目标,尽管操作名称 "moving"。在平凡的移动右手边(正式)不是 const,但整个操作的平凡性要求右手边(实际)不可改变,不是吗?在我看来,这意味着平凡的复制操作和平凡的移动操作在它们的深层本质上完全相同(在内存、内存布局、位等方面)。我说得对吗?

如果是这样,那么我想,如果我在用户代码中看到平凡的移动构造类型,而不是平凡的复制构造类型,那么我显然看到了一些反模式。我说得对吗?

有没有这种人造但可用的类型的例子,它不是平凡的copy-constructible/assignable,而是平凡的move-constructible/assignable?

是否存在一个类型可以有一个普通的复制构造函数而没有一个普通的移动构造函数的用例?当然。

例如,拥有一个在移动时始终为空的指针包装类型可能很有用。复制构造函数没有理由不重要,但移动构造函数必须将旧值设置为 NULL。

template<typename T>
class empty_on_move
{
  T *ptr_;

public:
  empty_on_move(const empty_on_move&) = default;
  empty_on_move(empty_on_move &&other) : ptr_(other.ptr_) {other.ptr_ = nullptr;}
...
};

empty_on_move 不拥有该对象,这就是为什么可以拥有它的多个副本的原因。它的存在只是为了确保当您离开它时,指针处于易于理解的状态。因此,is_trivially_copy_constructible<empty_on_move<T>> 为真,而 is_trivially_move_constructible<empty_on_move<T>> 为假。

它主要用于其他 类 内部,这些 类 想要给出特定行为的指示。这样,您就不必明确地将代码写入他们的移动 constructors/assignments 以将这些字段设为 NULL。


话虽这么说,你真的问错了问题。为什么?因为答案并不重要。

只有当您需要 Trivially Copyable 类型时,copy/move constructor/assignment 的琐碎性才有意义。 属性 允许使用 memcpy 和类似的东西,而不是个别操作的琐碎。平凡可复制的 属性 要求 copy/move constructor/assignment 和析构函数 all 是平凡的(或删除,从 C++14 开始)。

如果您正在围绕某种类型编写包装器(或编写 sum/product 类型),并且您想要公开该类型的属性,您只需要关心公开平凡可复制性。也就是说,如果 T(或 Ts...)是平凡可复制的,那么你的类型也应该是平凡可复制的。

但除此之外,你不应该仅仅因为 T 就觉得需要一个简单的复制构造函数。