fou*_*nes 7 c++ language-lawyer c++17
在C++17中,考虑这样一种情况:S一个结构体删除了默认构造函数,并且有一个float成员,当S用空大括号初始化时,标准是否保证float成员被零初始化?
struct A {
int x{};
};
struct S
{
S() = delete;
A a;
float b;
};
int main()
{
auto s = S{}; // Is s.b guaranteed to be zero?
}
Run Code Online (Sandbox Code Playgroud)
在我看来,cppreference.com 并不清楚,都说:
如果初始值设定项子句的数量小于成员的数量且基数或初始值设定项列表完全为空,则剩余的成员和基数 (C++17 起) 将由其默认成员初始值设定项(如果在类定义中提供)进行初始化,否则(C++14 起) 根据通常的列表初始化规则,从空列表进行复制初始化(对具有默认构造函数的非类类型和非聚合类执行值初始化,并对聚合执行聚合初始化)。如果引用类型的成员是这些剩余成员之一,则程序格式错误。
(来自这里),这意味着 b 保证为零
在所有情况下,如果使用空大括号对 {} 并且 T 是聚合类型,则执行聚合初始化而不是值初始化。
(从这里)
这意味着 b 不能保证为零。
还有一个讨论似乎暗示虽然不能保证,但所有已知的编译器都会进行零初始化:
该标准指定,当类具有用户提供或删除的默认构造函数时,不会执行零初始化,即使重载决策未选择该默认构造函数也是如此。如果选择了未删除的默认默认构造函数,所有已知的编译器都会执行额外的零初始化。
这是 C++ 的一个怪癖,已在 C++20 中修复。同时,您可以添加explicit到已删除的默认构造函数以强制该结构变为非聚合,并使您的代码有保证的编译错误:
struct A {
int x{};
};
struct S
{
explicit S() = delete;
const A a;
const float b;
};
int main()
{
auto s = S{}; // error: call to deleted constructor of 'S'
}
Run Code Online (Sandbox Code Playgroud)
因为S是聚合,S{}所以会执行聚合初始化。标准中关于当列表中没有初始化器时如何初始化成员的规则基本上就是您引用的内容:
- 如果该元素具有默认成员初始值设定项 ([class.mem]),则该元素将从该初始值设定项初始化。
- 否则,如果该元素不是引用,则该元素将从空初始值设定项列表 ([dcl.init.list]) 中复制初始化。
所以对于 b 来说,这相当于float b = {};。根据列表初始化的规则,我们必须一直降到 3.10:
否则,如果初始值设定项列表没有元素,则对该对象进行值初始化。
并且值初始化会将a初始化float为0。