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++ 静态初始化顺序的主要内容,如果未能解决你的问题,请参考以下文章