根据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
?
答案 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
……这将是唯一的候选者……所以移动将调用副本构造函数。移动会复制!这也不是少见的事情-移动没有具有破坏性或比复制更有效,只是可以。
广义上讲,有四个理智的类型类别:
Person const&
)int
或string
)vector<int>
)unique_ptr<int>
)这里有许多类型属于第1组。 OP中提到的变量类型也应该属于第1组。
此列表中明显缺少的是可复制但不可可移动的类型,因为从操作的角度来看这没有多大意义。如果您可以复制该类型,并且不想在移动时具有破坏性的行为,则只需移动也复制该类型即可。
这样,您可以将这些组视为一种层次结构。 (3)在(4)上展开,而(1)和(2)在(3)上展开-您不能真正从句法上区分(1)与(2)。因此,可复制的包含项是可移动的。