类层次结构中的静态初始化顺序

Static initialization order in class heirarchy

本文关键字:初始化 顺序 静态 层次结构      更新时间:2023-10-16

我最近痛苦地意识到静态初始化顺序的惨败。我想知道"跨翻译单元未定义初始化顺序"的规则是否仍然适用于父类中的静态成员,而子类中的静态成员需要这些成员。

例如,假设我们有(为简洁起见,排除所有 # 守卫和包含(

// a.h
class A {
    static int count;
    static int register_subclass();
};
// a.cpp
int A::count = 0;
int A::register_subclass() {
    return count ++;
}

然后是A的一个子类,

// b.h
class B : public A {
    static int id;
};
// b.cpp
int B::id = A::register_subclass();

这里有两个翻译单元,一个是静态对象,另一个是初始化时的静态对象......似乎这可能是静态初始化订单惨败的一个实例。

我的问题是:它真的安全吗

也就是说,我是否可以保证B::id在初始化A::count之前不会包含从复制的垃圾?从我自己的测试来看,A似乎总是首先初始化,但我不确定如何在初始化顺序中引入噪音以增加行为未定义时失败的可能性。

通常,依赖基类和派生类的静态初始化顺序是不安全的。不能保证A的静态初始化将在B之前发生。这就是静态初始化顺序惨败的定义。

您可以在首次使用惯用语时使用该构造:

// a.h
class A {
private:
    static int& count();
protected:
    static int register_subclass();
};
// a.cpp
int& A::count() {
    static int count = 0;
    return count;
}
int A::register_subclass() {
    return count()++;
}
// b.h
class B : public A {
public:
    static int id;
};
// b.cpp
int B::id = A::register_subclass();

现场演示。

更新:然而,说到这里,波格丹在评论中指出

根据标准中的 [3.6.2],保证了此特定示例中的初始化顺序。它与继承无关,而是与A::count的初始化是常量初始化有关,保证在动态初始化之前完成,这是B::id使用的。

但是,除非您完全掌握了此类内部功能,否则我建议您在首次使用惯用语时使用该构造。

在这种情况下没关系,但要小心多线程上下文中的A::register_subclass等函数。如果多个线程同时调用它,则可能发生任何事情。

我想知道"跨翻译单元未定义初始化顺序"的规则是否仍然适用于父类中的静态成员,而子类中的静态成员需要这些成员。

是的,确实如此。

静态数据成员与继承层次结构(或者实际上,它们的封装类(相关的唯一方式是它们的完全限定名称;它们的定义和初始化完全不知道/不关心这一点。