C++ 静态初始化顺序

Posted

技术标签:

【中文标题】C++ 静态初始化顺序【英文标题】:C++ static initialization order 【发布时间】:2010-11-03 14:10:01 【问题描述】:

当我在 C++ 中使用静态变量时,我经常想要初始化一个变量并将另一个变量传递给它的构造函数。换句话说,我想创建相互依赖的静态实例。

在单个 .cpp 或 .h 文件中,这不是问题:实例将按照声明的顺序创建。但是,当您想用另一个编译单元中的实例初始化静态实例时,似乎无法指定顺序。结果是,根据天气情况,可能会构建依赖于另一个实例的实例,然后才构建另一个实例。结果是第一个实例初始化不正确。

有谁知道如何确保以正确的顺序创建静态对象?我已经搜索了很长时间的解决方案,尝试了所有解决方案(包括 Schwarz Counter 解决方案),但我开始怀疑是否有一个真正有效的解决方案。

一种可能性是使用静态函数成员的技巧:

Type& globalObject()

    static Type theOneAndOnlyInstance;
    return theOneAndOnlyInstance;

确实,这确实有效。遗憾的是,您必须编写 globalObject().MemberFunction(),而不是 globalObject.MemberFunction(),导致客户端代码有些混乱和不优雅。

更新:感谢您的反应。遗憾的是,似乎我确实已经回答了我自己的问题。我想我必须学会忍受它......

【问题讨论】:

实例将按照它们的顺序创建 定义 【参考方案1】:

您已经回答了自己的问题。静态初始化顺序是未定义的,最优雅的解决方法(同时仍然进行静态初始化,即不完全重构它)是将初始化包装在一个函数中。

阅读从https://isocpp.org/wiki/faq/ctors#static-init-order开始的C++ FAQ项目

【讨论】:

不是在主输入之前,而是在方法第一次被调用时。见blogs.msdn.com/b/oldnewthing/archive/2004/03/08/85901.aspx @enobayram:带你回到 2.1 年前 :),是的,这是个问题。将初始化移动到函数中的全部意义在于,现在初始化main 之前发生,而是在第一次调用函数时发生,这可能是在程序执行期间的任何时候......并在任何线程中。 @Lightness,Jose,Charles:我真的认为您的 cmets 应该包含在公认的答案中。随着多线程在今天变得司空见惯,这可能会导致令人讨厌的意外,并且在静态初始化惨败的任何热门搜索结果中都没有提到。即 parashift 根本没有提到它。 线程安全在 C++11 中不是问题。见***.com/questions/8102125/… @laalto,阅读 C++ FAQ 让我们回到了“你读过手册页”的日子。【参考方案2】:

也许您应该重新考虑是否需要这么多全局静态变量。虽然它们有时很有用,但将它们重构到更小的局部范围通常要简单得多,尤其是当您发现某些静态变量依赖于其他变量时。

但是你是对的,没有办法确保特定的初始化顺序,所以如果你下定决心,像你提到的那样,将初始化保持在一个函数中,可能是最简单的方法。

【讨论】:

你是对的,使用过多的全局静态变量是不明智的,但在某些情况下,它可以避免过度传递同一个对象。想想记录器对象、持久变量的容器、所有 IPC 连接的集合等等...... “避免过度传递对象”只是一种说法,“我的程序组件之间的依赖关系非常广泛,以至于跟踪它们是过多的工作。所以最好停止跟踪它们”。除了通常情况并非如此——如果无法简化依赖关系,那么通过跟踪对象传递的位置来跟踪它们是有用的。【参考方案3】:

确实,这确实有效。遗憾的是,您必须编写 globalObject().MemberFunction(),而不是 globalObject.MemberFunction(),导致客户端代码有些混乱和不优雅。

但最重要的是它有效,并且它是失败证明,即。绕过正确的用法并不容易。

程序的正确性应该是您的首要任务。此外,恕我直言,上面的 () 纯粹是文体 - 即。完全不重要。

根据您的平台,请注意过多的动态初始化。动态初始化程序可以进行相对少量的清理(请参阅here)。您可以使用包含不同全局对象成员的全局对象容器来解决此问题。因此,您有:

Globals & getGlobals ()

  static Globals cache;
  return cache;

只有一次调用 ~Globals() 来清理程序中的所有全局对象。为了访问全局,您仍然需要:

getGlobals().configuration.memberFunction ();

如果你真的想要,你可以将它包装在一个宏中,以节省使用宏输入的一点点:

#define GLOBAL(X) getGlobals().#X
GLOBAL(object).memberFunction ();

虽然,这只是您最初解决方案的语法糖。

【讨论】:

【参考方案4】:

大多数编译器(链接器)实际上都支持一种(不可移植的)指定顺序的方式。例如,使用 Visual Studio,您可以使用 init_seg pragma 将初始化安排到几个不同的组中。 AFAIK 无法保证每个组内的顺序。由于这是不可移植的,您可能需要考虑是否可以将您的设计修复为不需要它,但选项就在那里。

【讨论】:

【参考方案5】:

尽管这个线程的年龄,我想提出我找到的解决方案。 正如我之前很多人指出的那样,C++ 没有提供任何静态初始化排序机制。我建议将每个静态成员封装在类的静态方法中,该方法依次初始化成员并以面向对象的方式提供访问。 让我举个例子,假设我们要定义名为“Math”的类,该类在其他成员中包含“PI”:

class Math 
public:
   static const float Pi() 
       static const float s_PI = 3.14f;
       return s_PI;
   

s_PI 将在 Pi() 方法第一次被调用时被初始化(在 GCC 中)。请注意:具有静态存储的本地对象具有依赖于实现的生命周期,有关更多详细信息,请查看2 中的 6.7.4。

Static keyword, C++ Standard

【讨论】:

这与 OP 的方法有何不同,除了您将函数设为类的成员? 也不是一个有启发性的例子,因为这样的对象可以而且应该是constexpr【参考方案6】:

将静态包装在方法中将解决顺序问题,但正如其他人指出的那样,它不是线程安全的,但如果这是一个问题,您也可以这样做以使其成为线程。

// File scope static pointer is thread safe and is initialized first.
static Type * theOneAndOnlyInstance = 0;

Type& globalObject()

    if(theOneAndOnlyInstance == 0)
    
         // Put mutex lock here for thread safety
         theOneAndOnlyInstance = new Type();
    

    return *theOneAndOnlyInstance;

【讨论】:

最佳答案恰恰表明了这一点,并且在近 5 年前就已经回答了。或许我们应该把它作为一个例子移到最佳答案中。 这在 C++11 中不是问题。见***.com/questions/8102125/…

以上是关于C++ 静态初始化顺序的主要内容,如果未能解决你的问题,请参考以下文章

C++实现静态顺序表的增删查改以及初始化

静态变量初始化顺序

静态变量初始化顺序

Java的初始化块静态初始化块构造函数的执行顺序及用途探究

C++ 并发销毁

go包初始化顺序