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

时间:2020-07-09 08:23:14

标签: c++ copy move-semantics c++20

根据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

2 个答案:

答案 0 :(得分:3)

这来自两个事实。首先,即使您没有定义移动构造函数+移动分配,如果您定义复制函数,您仍然可以从r值引用构造/分配对象。只需看一下示例:

#include <utility>

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

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

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

请注意,由于我声明了复制构造函数,因此编译器DID不会生成默认的move构造函数。

答案 1 :(得分:1)

虽然复制该变量是有意义的(例如,在调用另一个函数之前保存其状态),但将其移动是没有意义的,实际上非常糟糕,因为其他函数可能不知道该变量当前在一个未指定的状态。

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

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; }
};

移动{em {1}} 会做什么?好吧,类型为Person的右值可以绑定到Person……这将是唯一的候选者……所以移动将调用副本构造函数。移动会复制!这也不是少见的事情-移动没有具有破坏性或比复制更有效,只是可以

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

  1. 对其进行移动和复制的类型执行相同的操作(例如Person const&
  2. 可以将移动作为优化消耗资源的副本的类型(例如intstring
  3. 可以移动但不能复制的类型(例如vector<int>
  4. 不能移动或复制的类型(例如unique_ptr<int>

这里有许多类型属于第1组。 OP中提到的变量类型也应该属于第1组。

此列表中明显缺少的是可复制但不可可移动的类型,因为从操作的角度来看这没有多大意义。如果您可以复制该类型,并且不想在移动时具有破坏性的行为,则只需移动复制该类型即可。

这样,您可以将这些组视为一种层次结构。 (3)在(4)上展开,而(1)和(2)在(3)上展开-您不能真正从句法上区分(1)与(2)。因此,可复制的包含项是可移动的。