【问题标题】:Static initialization order in class heirarchy类层次结构中的静态初始化顺序
【发布时间】:2015-10-17 23:21:33
【问题描述】:

我最近痛苦地意识到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::count 复制的垃圾?从我自己的测试来看,A 似乎总是首先被初始化,但我不确定如何在初始化顺序中引入噪音以增加如果行为未定义的失败概率。

【问题讨论】:

  • 没有。 a.cpp 和 b.cpp 链接到可执行文件的顺序是保证的。那就是的“初始化顺序惨败”。 (您的链接器可能使用字母顺序,也可能不使用)。
  • @BoPersson 我很害怕。谢谢。将此作为答案(也许提供参考?),我会将其标记为已接受
  • @BoPersson:答案去那里哥们↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

标签: c++ initialization static-initialization initialization-order


【解决方案1】:

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

你可以使用 construct on first use 成语:

// 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();

Live demo.

更新:不过话说回来,bogdan 在 cmets 中指出

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

但除非你完全掌握了这些细节,否则我建议你使用 construct on first use 习惯用法。

在这种情况下没问题,但要注意多线程上下文中的 A::register_subclass 之类的函数。如果多个线程同时调用它,任何事情都可能发生。

【讨论】:

  • 谢谢 - 这基本上是我最终使用的解决方案。太糟糕了,必须在我可怜的、简单的小变量周围添加那个讨厌的静态函数。
  • 其实根据标准中的[3.6.2],在OP的具体例子中初始化的顺序是有保证的。它与继承无关,而是A::count的初始化是常量初始化,保证在动态初始化之前完成,这就是B::id 使用。但是,count ++ 在存在线程的情况下可能会成为问题 - B::id 和其他一些 C::id 的初始化可能会同时运行。抄送@stett
  • 使用本地statics 的建议通常是合理的,但我认为为了准确起见,可能值得在答案中澄清上述细节。
  • −1 初始化int A::count = 0; 是完全多余的,因为A::count 已经被零初始化。 IE。 (1) 定义 int A::count; 就足够了,并且 (2) 这确保了示例的安全性。这意味着这个答案的断言“通常,这不安全”是错误的。
  • @Cheersandhth.-Alf 我试图更清楚地说明我所说的“一般”是什么意思。
【解决方案2】:

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

是的,确实如此。

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

【讨论】:

    猜你喜欢
    • 2010-12-02
    • 1970-01-01
    • 1970-01-01
    • 2018-12-19
    • 1970-01-01
    • 2014-09-07
    • 2011-01-07
    • 1970-01-01
    • 2021-07-12
    相关资源
    最近更新 更多