根据cppreference,std::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
呢?
这来自两个事实。首先,即使您没有定义移动构造函数+移动赋值,如果您定义了复制函数,您仍然可以从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);
}
其次(也许更重要的是(,可复制类型总是可以(或者根据现在的标准必须是(以某种方式移动。若对象是可复制的,那个么移动的最坏情况就是复制内部数据。
注意,由于我声明了复制构造函数,编译器没有生成默认的移动构造函数。
虽然复制该变量是有意义的(例如,在调用另一个函数之前保存其状态(,但移动它毫无意义,实际上会非常糟糕,因为其他函数可能不知道该变量当前处于未指定状态。
对于移动的实际含义,有一个强有力的、未说明的假设,这可能是混乱的根源。考虑类型:
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&
结合。。。那将是唯一的候选人。。。因此移动将调用复制构造函数。移动复制!这也不是一种罕见的情况-移动不具有的破坏性或比复制更有效,它只是可以。
从广义上讲,有四类正常的类型:
- 移动和复制执行相同操作的类型(例如
int
( - 移动可以是消耗资源的副本优化的类型(例如
string
或vector<int>
( - 可以移动但不能复制的类型(例如
unique_ptr<int>
( - 既不能移动也不能复制的类型(例如
mutex
(
有很多类型属于第1组。OP中提到的变量也应该属于第1组。
值得注意的是,该列表中缺少一个可复制但不能移动的类型,因为从操作角度来看,这几乎没有意义。如果您可以复制类型,并且不希望移动时出现破坏性行为,只需使移动也复制类型即可。
因此,您可以将这些组视为一种层次结构。(3( 在(4(上展开,(1(和(2(在(3(上展开——你无法从语法上真正区分(1(与(2(。因此,可复制包含可移动。