防止静态初始化命令“惨败”,C++

Posted

技术标签:

【中文标题】防止静态初始化命令“惨败”,C++【英文标题】:Prevent static initialization order "fiasco", C++ 【发布时间】:2015-07-01 13:32:11 【问题描述】:

有一次我阅读了一篇很棒的 C++ FAQ(真的很棒!!)并阅读了 topic 关于如何防止静态初始化命令“惨败”的文章。所以作者建议将静态变量包装成函数,从而通过维护变量的创建顺序来防止“惨败”。但这在我看来是一个粗鲁的解决方法。所以我的问题是,是否有任何现代的、更面向模式的方法来防止这种“惨败”,而是将“静态的东西”包装成函数???

【问题讨论】:

防止失败的优雅方法是永远不要使用依赖于任何东西的静态对象。 FAQ 中提供了关于Construct on first use idiom 的建议。许多 C++ 程序员都熟悉的模式。它易于实现,甚至更易于使用。我不明白您所说的“现代、更面向模式的方式”是什么意思。 另见AddressSanitizerInitializationOrderFiasco。 【参考方案1】:

所以我的问题是,是否有任何现代的、更面向模式的方法来防止这种“惨败”,而是将“静态的东西”包装到函数中???

在大多数情况下,您可以在主函数中声明“全局”数据,并在需要时使用依赖注入来传递它。换句话说,根本就没有静态。

在实践中,您可能会遇到需要静态数据的情况。如果对其他静态没有依赖,则将静态数据设为const/constexpr

// smart pointer that implements the "Foo" release policy
class FooPointer

    static const FooPointer NullFoo; // does not depend on other static values
    /* ... */
;

如果静态变量 do 相互依赖,只需将它们包装在静态函数中即可:

// smart pointer that implements the "Foo" release policy
class FooPointer

    static const FooPointer& NullFoo(); // depends on other static values
    /* ... */
;

总结一下:

大多数(90%?99%?)静态/全局/共享数据应该依赖注入到它使用的地方,而不是创建为静态的。

在极少数情况下,当出于某种原因需要静态且它们不依赖于其他静态时,请声明静态变量。

非常极少数情况下,当静态需要是静态的并且它们相互依赖时,请在静态方法中交换它们。

根据经验,如果您有很多第二种和第三种情况,那么您在第一种情况下做得还不够。

【讨论】:

【参考方案2】:

解决问题的更常用方法是尽可能避免静态 - 在依赖于构造顺序的对象之间更是如此。

然后按要求的顺序构造对象。例如,如果我们有两个对象 x 和 y,如果 x 尚未构造,y 的构造将失败,则先构造 x 并将其提供给 y 的构造函数(或另一个成员))

 SomeObject x;
 SomeOtherObject y(x);

 SomeObject *x = new SomeObject;
 SomeOtherObject y = new SomeObject(*x);   

(以上都假设y的构造函数需要引用)。

如果您需要在函数之间共享xy,只需将它们作为参数传递给函数即可。

如果您必须使用静态变量(即您不希望在任何地方都键入传递参数),则将静态变量设为指针,并将它们初始化一次(例如,在 main() 中)。

//  all source files can use x and y via these declarations  (e.g. via a header file)

extern SomeObject *x;
extern SomeOtherObject *y;

//  definition in one source file only

SomeObject *x;
SomeOtherObject *y;

int main()

     x = new SomeObject;
     y = new SomeOtherObject(*x);

       // call other functions that use x and y.

     delete y;
     delete x;

但是,实际上,最好尽可能避免使用静态。

【讨论】:

+1 这比第一次使用成语恕我直言的构造更好。当扩展到更大的项目时,一个可能的改进是使用初始化和访问器函数(隐藏指针并断言初始化已完成)并按库对初始化进行分组。然而,基本的想法是相同的:控制初始化和清理的时间,不要让它随机出现。【参考方案3】:

现代的、更面向模式的方式是一开始就不使用全局变量

没有其他办法。

否则,这不会是一场“惨败”!

【讨论】:

很遗憾,这是不现实的。它对典型的 repo 或 GNU 的 FTP 站点中的数千个软件包没有帮助。 @jww:唯一合理的选择是按照 OP 自己在问题中的建议去做。 这是一个错误的选择。它解决了问题,因此在析构函数中经历了崩溃;而不是构造函数。当一个物体消失得太快时,我亲身经历了 dtor 的崩溃。 @jww 有这个问题的代码存在严重的相互依赖问题,应该重构。

以上是关于防止静态初始化命令“惨败”,C++的主要内容,如果未能解决你的问题,请参考以下文章

防止在 C++ 中构造/初始化数组元素

C++ 静态初始化顺序

为啥我无法在 C++ 中初始化静态字段 [重复]

C++静态成员变量及其初始化

静态成员变量初始化 C++

C# 与 C++ 静态数组中静态常量列表初始化的效率