4

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

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

// 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似乎总是首先被初始化,但我不确定如何在初始化顺序中引入噪声以增加如果行为未定义的失败概率。

4

2 回答 2

4

通常,依赖基类和派生类的静态初始化顺序是不安全的。不能保证 的静态初始化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多线程上下文中的函数。如果多个线程同时调用它,任何事情都可能发生。

于 2015-10-18T11:41:07.893 回答
0

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

是的,它确实。

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

于 2015-10-18T12:55:28.223 回答