为什么 std::copyable 包含 std::movable?

Why does std::copyable subsume std::movable?

根据cppreferencestd::copyable定义如下:

template <class T>
concept copyable =
  std::copy_constructible<T> &&
  std::movable<T> && // <-- !!
  std::assignable_from<T&, T&> &&
  std::assignable_from<T&, const T&> &&
  std::assignable_from<T&, const T>;

我想知道为什么可复制的对象也应该是可移动的。只需考虑一个由多个函数访问的全局变量。虽然复制那个变量是有意义的(例如在调用另一个函数之前保存它的状态)但是移动它是没有意义的,而且实际上是非常糟糕的,因为其他函数可能不知道该变量当前处于未指定状态.那么为什么 std::copyable 包含 std::movable 呢?

这来自两个事实。首先,即使你没有定义移动构造函数+移动赋值,如果你定义了复制函数,你仍然可以 construct/assign 来自右值引用的对象。看看这个例子:

#include <utility>

struct foo {
    foo() = default;
    foo(const foo&) = default;
    foo& operator=(const foo&) = default;
};

int main()
{
    foo f;
    foo b = std::move(f);
}

其次(也许更重要),可复制类型始终可以(或根据标准现在必须)以某种方式移动。如果对象是可复制的,那么移动的最坏情况就是复制内部数据。

请注意,由于我声明了复制构造函数,编译器并未生成默认的移动构造函数。

While it makes sense to copy that variable (for example to save its state before calling another function) it makes no sense, and actually would be very bad, to move it since other functions might not know that that variable is currently in an unspecified state.

这里有一个强烈的、未说明的假设,即移动的实际含义,这可能是混淆的根源。考虑类型:

class Person {
    std::string name;
public:
    Person(std::string);
    Person(Person const& rhs) : name(rhs.name) { }
    Person& operator=(Person const& rhs) { name = rhs.name; return *this; }
};

移动一个Person有什么作用?好吧,Person 类型的右值可以绑定到 Person const&... 那将是唯一的候选者... 所以移动会调用复制构造函数。移动做一个副本!这也不是罕见的事情 - 移动并不 具有破坏性或比复制更有效,它只是 可以

从广义上讲,有四类理智的类型:

  1. 移动和复制执行相同操作的类型(例如 int
  2. 移动可以优化消耗资源的副本的类型(例如stringvector<int>
  3. 可以移动但不能复制的类型(例如unique_ptr<int>
  4. 既不能移动也不能复制的类型(例如mutex

那里有很多类型属于第 1 组。而OP中提到的那种变量也应该属于第1组。

值得注意的是,此列表中缺少一种可复制但不可可移动的类型,因为从操作的角度来看这没有什么意义。如果您可以复制类型,并且不希望在移动时出现破坏性行为,只需让移动复制类型。

因此,您可以将这些组视为一种层次结构。 (3) 在 (4) 上扩展,(1) 和 (2) 在 (3) 上扩展——你无法在句法上真正区分 (1) 和 (2)。因此,可复制包含可移动。