【问题标题】:Order of initialization with mixed inline and constructor initializations混合内联和构造函数初始化的初始化顺序
【发布时间】:2021-06-12 14:08:42
【问题描述】:
[这个问题来自this question]
考虑以下程序:
#include <iostream>
struct Foo
{
const size_t size;
int* arr{ create_arr(size) };
Foo(size_t s)
: size{ set_size(s) }
{
}
static int* create_arr(size_t s)
{
std::cout << "Creating array with size " << s << '\n';
return new int[s];
}
static size_t set_size(size_t s)
{
std::cout << "Setting size to " << s << '\n';
return s;
}
};
int main()
{
Foo f(10);
}
arr 的内联初始化取决于size 的值。但是size的值只在构造函数初始化列表中初始化。
Both GCC and Clang handles it correctly,如果size和arr的声明顺序被镜像会报错。我还听说 MSVC 会做“正确”的事情(根据 the other question 中的 cmets)。
我的问题是:这是明确定义的吗?
我知道初始化是按声明顺序完成的,但它是否也包括内联初始化?
【问题讨论】:
-
正如我在另一个问题中评论的那样,将代码转到constexpr,(大多数 UB 都被检测到,因为它们不会发生)。程序按预期运行Demo。
标签:
c++
initialization
language-lawyer
【解决方案1】:
我知道初始化是按声明顺序完成的,但确实如此
也包括内联初始化?
是的,必须的,见class.base.init:
13 在非委托构造函数中,初始化在
以下顺序: (13.1) — 首先,并且仅适用于
大多数派生类(4.5),虚拟基类在
它们出现在深度优先从左到右的遍历中的顺序
基类的有向无环图,其中“从左到右”是
派生类中基类的出现顺序
基本说明符列表。 (13.2) — 那么,直接基类是
按照声明顺序初始化,因为它们出现在
base-specifier-list(不管 mem-initializers 的顺序如何)。
(13.3) — 然后,按顺序初始化非静态数据成员
它们是在类定义中声明的(同样不管
mem-initializers 的顺序)。 (13.4) — 最后,
执行构造函数主体的复合语句。 [注:
声明令的任务是确保 base 和 member sub
虽然这里的标准不是太具体,但这应该是完全足够的,因为默认的成员初始化器是初始化器。类定义中的顺序始终是这里的基础。在初始化方面,这里有一个例外与标准的许多其他部分极为矛盾。
在本节中还有一个附加说明,该标准通过对破坏的关注强调了这一点:
[注:声明顺序的任务是确保基础和
成员子对象以相反的顺序被销毁
初始化。 ——尾注]
所以这里根本没有进一步解释的余地。
【解决方案2】:
是的,它定义明确。我将在这里使用 C++20 的 N4860 草案作为参考。
11.10.2 初始化基和成员 [class.base.init] §9 说(强调我的):
在非委托构造函数中,如果给定的潜在构造子对象未由 meminitializer 指定-
id(包括因为构造函数没有ctorinitializer而没有mem-initializer-list的情况),
那么
(9.1) — 如果实体是具有默认成员初始值设定项 (11.4) 的非静态数据成员,并且
(9.1.1) — 构造函数的类是联合 (11.5)...
(9.1.2) — 构造函数的类不是联合,并且,如果实体是匿名联合的成员...
(9.2) — 否则,如果实体是匿名联合或变体成员 (11.5.1),...
(9.3) — 否则,实体默认初始化 (9.4)。
这与 11.4 类成员 [class.mem] §10 一致
大括号或等号初始化器只能出现在数据成员的声明中。 (对于静态数据成员,
见 11.4.8.2;对于非静态数据成员,见 11.10.2 和 9.4.1)。 用于非静态的大括号或等号初始化器
数据成员为成员指定一个默认的成员初始化器...
最后,我们发现在 11.10.2 初始化基和成员 [class.base.init] §13 说(强调我的):
13 在非委托构造函数中,初始化按以下顺序进行:
(13.1) — 首先,并且仅对于最派生类 (6.7.2) 的构造函数,虚拟基类在
它们出现在基类的有向无环图的深度优先从左到右遍历中的顺序,
其中“从左到右”是派生类基说明符列表中基类的出现顺序。
(13.2) — 然后,直接基类按照它们出现在基说明符列表中的声明顺序进行初始化
(无论内存初始化器的顺序如何)。
(13.3) — 然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化
(同样不管内存初始化器的顺序)。
(13.4) — 最后,执行构造函数主体的复合语句。
[注意:强制声明顺序是为了确保基子对象和成员子对象在
初始化的相反顺序。 — 尾注]
我的理解是类的成员按声明顺序初始化无论他们是否使用mem-initializer。如果一个成员没有mem-initializer但有一个bracket-or-equal-initializer,它将默认从那个bracket-or-equal-initializer在成员之后初始化在初始化之前声明。
因此,如果我正确阅读了 11.4 和 11.2 子条款,则相关代码具有明确定义的行为。