为什么删除移动构造函数导致向量停止工作

MK.*_*MK. 10 c++ c++11

如果我在类中禁止移动构造函数,我就不能再在向量中使用它:

class Foo
{
  public:
      Foo(int i) : i_(i) {}
      Foo(Foo&&) = delete;

      int i_;
};

int main()
{
    std::vector<Foo> foo;
    foo.push_back(Foo(1));
}
Run Code Online (Sandbox Code Playgroud)

为什么会这样?

How*_*ant 38

摘要

不要删除移动成员.


假设您的编译器完全符合C++ 11,那么显式删除移动构造函数也会隐式声明以下内容:

Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
Run Code Online (Sandbox Code Playgroud)

也就是说,如果您声明一个移动构造函数(或移动赋值运算符),并且不声明复制成员,则它们将被隐式声明为已删除.所以你的完整类Foo就好像:

class Foo
{
  public:
      Foo(int i) : i_(i) {}
      Foo(Foo&&) = delete;
      Foo(const Foo&) = delete;             // implicitly declared
      Foo& operator=(const Foo&) = delete;  // implicitly declared

      int i_;
};
Run Code Online (Sandbox Code Playgroud)

现在vector<Foo>::push_back(Foo(1))要求FooMoveConstructible. MoveConstructible可以通过可访问的移动构造函数,甚至是可访问的复制构造函数来满足.但Foo两者都没有.修复你可以:

class Foo
{
  public:
      Foo(int i) : i_(i) {}
      Foo(const Foo&) = default;
      Foo& operator=(const Foo&) = default;

      int i_;
};
Run Code Online (Sandbox Code Playgroud)

即默认复制成员并删除已删除的移动成员.

通常,明确删除移动成员不是一个好主意.如果你想让一个类可以复制而不是"可移动",那么就像在C++ 03中一样声明:声明/定义你的复制成员.您可以让复制成员使用编译器生成= default,并且仍然将其视为用户声明.并且不要声明移动成员.不存在的移动成员与已删除的移动成员不同.

删除的移动成员意味着您无法构建Foorvalue的副本,即使复制构造函数可以正常工作也是如此.这很少是期望的意图.

即使您希望您的课程不可复制也不可移动,最好只删除复制成员并保留移动成员未声明(意味着它们不存在).如果您正在查看代码(包括您自己的代码),并且看到已删除的移动成员,那么它们几乎肯定是不正确的,或者是最好的多余和混乱.

有一天,有人会为删除的移动成员提供一个很好的用例.但这将是一个罕见的用例.如果您在代码中看到这样的模式,您应该期望代码作者有一个非常好的解释.否则,删除的移动成员可能只是不正确(充其量是多余的).但从好的方面来看,这个错误会在编译时显示,而不是在运行时(如在你的例子中).

下面是当您明确声明任何特殊成员时编译器将隐式执行的操作的摘要图表.那些红色的方块表示弃用的行为.

在此输入图像描述

= default= delete计入用户声明.

如果您想查看完整的幻灯片,请单击此处.