为什么添加移动构造函数会禁用初始化列表?
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
}
用简单的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
}