为什么添加移动构造函数会禁用初始化列表?

why does adding a move constructor disable initializier list?

用简单的struct比如

struct Foo { int i; };

我可以使用初始化列表创建一个新实例;无需编写构造函数:

Foo foo { 314 };

如果我现在添加移动构造函数

struct Bar
{
    int i;
    Bar(Bar&& other) { i = other.i; }
};

初始化程序不再有效,我也必须添加一个构造函数:

Bar(int i) : i(i) {}

我猜这种行为与 this answer (for user-defined move-constructor disables the implicit copy-constructor?) 有点相关,但最好提供更多详细信息。

编辑: 如答案所示,这与添加构造函数有关。如果我只添加一个移动运算符,这反过来似乎会造成各种不一致:

struct Baz
{
    int i;

    Baz& operator=(Baz&& other)
    {
        this->i = other.i;
        return *this;
    }
};

初始化程序再次工作,尽管 "move" 的语法略有不同(是的,这实际上是默认构造和移动赋值;但最终结果似乎大致相同):

Baz baz{ 3141 };
Baz b;
b = std::move(baz);

当没有构造函数时,此语法为 aggregate initialization,因为此结构是 聚合

添加构造函数后,此结构不再是聚合,无法使用聚合初始化。具体规则在list initialization中列出,相关的是:

The effects of list initialization of an object of type T are:

  • Otherwise, if T is an aggregate type, aggregate initialization is performed.
  • Otherwise, the constructors of T are considered, in two phases:...

因为您使用的是 aggregate initialization,即:

Aggregate initialization is a form of list-initialization, which initializes aggregates An aggregate is one of the following types: array type class type (typically, struct or union), that has

  • no private or protected non-static data members
  • no user-provided, inherited, or explicit (since C++17) constructors (explicitly defaulted or deleted constructors are allowed) (since C++11)
  • no virtual, private, or protected (since C++17) base classes
  • no virtual member functions

第 2 点使您的案例失败。

不是 initializer list 构造被移动构造函数禁用(开始时不存在),而是 聚合构造.并且有一个很好的理由:通过添加自定义构造函数,我们向编译器准确指示 中的 class 不是聚合 ,需要一些不同的东西,而不仅仅是对每个构造函数进行操作其成员一一。

对于聚合,即使成员变量具有非平凡类型,默认的 default、copy 和 move 构造函数也能正常工作。如果不能委托给他们,复制构造将自动删除,留下移动构造可用:

struct A { // non-copyable
  int a;
  int b;
  A(int a_, int b_): a(a_), b(b_) { std::cout << "A(int,int)\n"; }
  A() { std::cout << "A()\n"; }
  A(const A&) = delete;
  A(A&&) { std::cout << "A(A&&)\n"; }
};

struct B {
  A a;
};

int main() {
  B b1{{1,2}}; // OK: aggregate
  B b2{std::move(b1)}; // OK: calls A::A(A&&)
  //B b3{b1}; // error: B::B(const B&) auto-deleted
}

但是,如果您出于其他原因想要删除复制构造并保留其他默认值,请明确说明:

struct A { // copyable
  int a;
  int b;
  A(int a_, int b_): a(a_), b(b_) { std::cout << "A(int,int)\n"; }
  A() { std::cout << "A()\n"; }
  A(const A&) { std::cout << "A(const A&)\n"; }
  A(A&&) { std::cout << "A(A&&)\n"; }
};

struct B { // non-copyable
  A a;
  B() = default;
  B(const B&) = delete;
  B(B&&) = default;
};

int main() {
  B b1{{1,2}}; // OK: still an aggregate
  B b2{std::move(b1)}; // delegates to A::A(A&&)
  //B b3{b1}; // error
}