(有关此主题的详细介绍,请参阅博客文章 The fickle aggregate)
聚合初始化
Ax 类是 C++11、C++14 和 C++17 中的聚合,因为它没有用户提供的构造函数,这意味着 Ax{} 是 聚合初始化,绕过任何用户声明的构造函数,甚至被删除的构造函数。
struct NonConstructible {
NonConstructible() = delete;
NonConstructible(const NonConstructible&) = delete;
NonConstructible(NonConstructible&&) = delete;
};
int main() {
//NonConstructible nc; // error: call to deleted constructor
// Aggregate initialization (and thus accepted) in
// C++11, C++14 and C++17.
// Rejected in C++20 (error: call to deleted constructor).
NonConstructible nc{};
}
什么是聚合类的定义在各种标准版本(C++11 到 C++20)中发生了变化,这些规则可能会产生一些令人惊讶的后果。从 C++20 开始,特别是由于实现了
大多数经常令人惊讶的聚合行为已得到解决,特别是不再允许聚合具有用户声明的构造函数,对于一个类作为聚合的要求比仅仅禁止 用户提供的构造函数。
用户提供或仅用户声明的显式默认构造函数
请注意,提供显式默认(或已删除)的定义外线计为用户提供构造函数,这意味着在以下示例中,@ 987654327@ 有一个用户提供的默认构造函数,而A 没有:
struct A {
A() = default; // not user-provided.
int a;
};
struct B {
B(); // user-provided.
int b;
};
// Out of line definition: a user-provided
// explicitly-defaulted constructor.
B::B() = default;
结果A 是一个聚合,而B 不是。反过来,这意味着通过空 direct-list-init 对 B 进行初始化将导致其数据成员 b 处于未初始化状态。然而,对于A,相同的初始化语法将导致(通过A 对象的聚合初始化和其数据成员a 的后续值初始化)其数据成员a 的零初始化:
A a{};
// Empty brace direct-list-init:
// -> A has no user-provided constructor
// -> aggregate initialization
// -> data member 'a' is value-initialized
// -> data member 'a' is zero-initialized
B b{};
// Empty brace direct-list-init:
// -> B has a user-provided constructor
// -> value-initialization
// -> default-initialization
// -> the explicitly-defaulted constructor will
// not initialize the data member 'b'
// -> data member 'b' is left in an unititialized state
这可能会让人感到意外,并且显然存在读取未初始化数据成员 b 并导致未定义行为的风险:
A a{};
B b{}; // may appear as a sound and complete initialization of 'b'.
a.a = b.b; // reading uninitialized 'b.b': undefined behaviour.