【问题标题】: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,如果sizearr的声明顺序被镜像会报错。我还听说 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 子条款,则相关代码具有明确定义的行为。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-19
      • 1970-01-01
      • 2020-05-31
      • 1970-01-01
      • 1970-01-01
      • 2019-10-07
      • 2010-11-17
      相关资源
      最近更新 更多