【问题标题】:Why does std::copyable subsume std::movable?为什么 std::copyable 包含 std::movable?
【发布时间】:2020-10-29 18:59:08
【问题描述】:

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

【问题讨论】:

  • 一个有点相关的问题的有趣答案stackoverflow.com/a/14303116/6865932
  • 另请参阅原始移动语义提案中的此链接部分:open-std.org/jtc1/sc22/wg21/docs/papers/2002/…
  • 移动给定类型的任何对象的技术容量(即使我们错误地将“移动”限制为未实现为副本的移动) 并不意味着移动一个特定的 object 是一个好主意。您可能也可以分配给它;如果不应该这样做,请不要这样做。

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


【解决方案1】:

这来自两个事实。首先,即使您没有定义移动构造函数 + 移动赋值,如果您定义了复制函数,您仍然可以从 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);
}

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

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

【讨论】:

  • 即使认为右值可以绑定到 const 左值,您发布的代码仍然有效,因为编译器隐式生成了移动构造函数。如果您明确删除它,则代码将无法编译,并且您将获得一个可复制但不可移动的类型,由于您解释的原因,这没有任何意义。看起来概念不像类型特征那样关心每个特定情况,我们已经通过查看is_convertibleconvertible_tois_default_constructibledefault_initializable 等之间的差异看到了这一点......跨度>
  • 它是否与命名要求 CopyConstructibleCopyAssignable 也要求类型可移动有关?如果我没记错的话,命名的要求比相应的类型特征(is_copy_constructibleis_copy_assignable)更严格。
  • @user7769147 这是不正确的,如果有用户定义的复制函数,编译器不会生成移动函数。显式删除只会使编译器忽略 r-val ref 到 const l-val ref 的转换
  • @user7769147 我写了一个错误,我的意思是用户声明的复制构造函数会阻止移动构造函数的自动生成。它是用户声明的。
  • @user7769147:我同意 bartop 的观点。该标准对用户声明和用户定义非常挑剔。即使一个特殊的成员函数不是用户定义的,它仍然可以是用户声明的(默认情况下),在复制 ctor 的情况下将禁用移动 ctor。
【解决方案2】:

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

这里有一个强烈的、未说明的假设,即移动实际上意味着什么,这可能是混乱的根源。考虑类型:

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&amp;...,这将是唯一的候选...所以移动将调用复制构造函数。搬家做副本!这也不是罕见的情况 - 移动并不必须具有破坏性或比复制更有效,它只是可以

广义上讲,有四种健全的类型:

  1. 移动和复制的类型相同(例如int
  2. 移动可以是消耗资源的副本优化的类型(例如stringvector&lt;int&gt;
  3. 可以移动但不能复制的类型(例如unique_ptr&lt;int&gt;
  4. 既不能移动也不能复制的类型(例如mutex

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

此列表中明显缺少的是可复制但不可可移动的类型,因为从操作的角度来看,这几乎没有意义。如果您可以复制类型,并且您不希望在移动时产生破坏性行为,只需让移动复制类型。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-03
    • 2022-03-27
    相关资源
    最近更新 更多