虚拟成员函数的定义是否强制在同一转换单元中动态初始化静态数据成员?

Does the definition of a virtual member function force dynamic initialization of a static data member in the same translation unit?

本文关键字:单元 转换 动态 初始化 数据成员 静态 定义 函数 是否 虚拟成员      更新时间:2023-10-16

考虑以下两个翻译单元:

// foo.cpp
#include <iostream>
class Foo {
public:
virtual ~Foo() = default;
virtual void bar();
private:
static int _baz;
};

static int f() {
std::cout << "f calledn";
return 42;
}
int Foo::_baz = f();
void Foo::bar() {
std::cout << "Baz::bar calledn";
}

// main.cpp
#include <iostream>
int main() {
std::cout << "main calledn";
}

当将两个翻译单元编译为单个可执行文件时(例如,使用g++ -std=c++17 main.cpp foo.cpp时,选择优化级别或两个 cpp 文件的顺序无关紧要(,生成的可执行文件会打印

f 称为
主调用

无论使用三个主要编译器中的哪一个GCC,Clang和MSVC来编译它。您可以在魔杖盒上亲自查看行为。

我的问题是:即使程序中根本没有使用整个类Foo,标准是否保证Foo::_baz将被初始化(从而调用f

我相信是这样;我的理由如下:

根据 [basic.start.dynamic]/4,在执行 main 的第一个语句之前不必初始化Foo::_baz,但是

如果 [初始化] 被推迟,则它强烈发生在任何非初始化 ODR 使用任何非内联函数或非内联变量之前,该函数或非内联变量在与要初始化的变量相同的翻译单元中定义。

在这里,"非初始化 odr-use"定义为

[

...]不是由非本地静态或线程存储持续时间变量的初始化直接或间接引起的ODR使用([basic.def.odr](

在 [basic.start.dynamic]/3 中。但根据 [basic.def.odr]/7,

如果虚拟成员函数不是纯函数,则使用 ODR。

由此我得出结论,Foo::bar的定义是非初始化 odr 使用在与Foo::_baz相同的翻译单元中定义的非内联函数,因此Foo::_baz将被初始化。

我觉得这种推理的奇怪之处在于,Foo::_baz的延迟初始化需要在 ODR 使用Foo::bar之前发生,即在定义Foo::bar之前(wtf?!(,因为定义本身就是 odr 使用。这让我觉得我的推理可能有缺陷。

再说一遍:标准是否保证Foo::_baz将被初始化(从而调用f,即使其翻译单元中的任何内容都没有在程序中使用?如果是,我们是否可以说这何时会发生(考虑到在定义虚拟成员函数之前必须发生的奇怪的排序约束(,如果不是,我的推理错误在哪里?

静态初始化不受虚函数的影响,与动态初始化的关系是关于排序的,对于常量初始值设定项,它如[class.static.data]/2中所述

[注意:一旦定义了静态数据成员,即使没有创建其类的对象,它仍然存在。

然而[basic.start.dynamic]/4

具有

静态存储持续时间的非局部非内联变量的动态初始化是在 main 的第一条语句之前排序还是延迟,这是实现定义的。如果它被推迟,则在与要初始化的变量相同的翻译单元中定义的任何非内联函数或非内联变量的任何非初始化odr使用之前,它就会发生。

即使实现选择推迟初始化,标准要求它发生。 并作为前一条的脚注:

在这种情况下,初始化具有静态存储持续时间且具有副作用的非局部变量,即使它本身不是 odr 使用的。