确保C++中静态变量的构造和销毁顺序

5
我正在寻找一种确保静态变量的构造和销毁顺序的好方法。据我所知,静态变量的构造和销毁方式如下:
  1. 静态对象的销毁顺序与它们的构造顺序相反

  2. 如果在不同文件的全局空间中定义静态变量,则它们的构造顺序不能保证。

  3. 但是,如果一个静态变量在函数中定义,则第一次执行命中其声明时会构造局部静态变量

基于上述规则,我编写了以下c++代码以确保静态变量b始终在静态变量a之前被销毁,在我的实验中可以确保构造和销毁顺序:
在文件A.h中:
class A {
 public:
  SomeClass* GetStatic() {
    static SomeClass a;
    return &a;
  }
}

在B.h文件中:
#include "A.h"
class B {
 public:
  AnotherClass* GetStatic() {
    A::GetStatic();  // a dummy call to force the static local variable in 
                     // A::GetStatic() get initialized before the b.
    static AnotherClass b;
    return &b;
  }
}

在上面的例子中,我在声明static AnotherClass b;之前放置了一个虚拟调用A :: GetStatic();。如果规则3适用,则可以确保ab之前初始化。由于规则1,可以保证ba之前被销毁。
我的问题是:
  1. 我能否知道我所做的是否正确,或者在某些边角情况下可能出错?
  2. 是否有更好或最佳的方法来确保静态变量的构造或销毁顺序?
我还查看了 isocpp.org网站以获取确保静态变量构造和销毁顺序的最佳方法,但该部分仍标记为TODO:编写此内容。

谢谢您的快速回复!我可以知道为什么在这种情况下没有定义销毁顺序,以及为什么规则1不适用于这种情况吗?如果有参考资料会更好。 - keelar
我只是挑刺一下,我认为你应该在getStatic函数中使用引用而不是指针作为返回值。原因有很多(比如更好的可读性,无需检查nullptr,因为我这么说了;-))。 - Superlokkus
@Mats Petersson:感谢您的澄清!“编译器将构建要调用的析构函数列表。它不会动态执行此操作”--- 您是否有支持此部分的C++文档/参考资料? - keelar
这太棒了!谢谢T.C!您介意把它们放在答案中而不是评论中吗?我认为这比评论更有价值。 - keelar
显示剩余8条评论
2个回答

4
在您的情况下,您正在首次使用构造函数,并且您的类的任何构造函数都不依赖于其他构造函数。因此,初始化顺序保证为A然后B。
在这种情况下,销毁顺序保证为B->A,只要您有简单的析构函数。这里有一个更详细的答案。

我能进一步了解在这种情况下,由于其构造顺序为A然后B,销毁顺序是否保证为B然后A吗? - keelar
@keelar 对不起,我没有在答案中包含那个,因为在第一次阅读时它不是问题的一部分,但也许我错过了。对于你的情况,第二种COFU表单是不确定的。 - g24l
1
此外,阅读[这个答案][2]是值得的,特别是[评论部分][3]。 [2]: https://dev59.com/_Gkw5IYBdhLWcg3wVpBi#9968204 [3]: https://dev59.com/_Gkw5IYBdhLWcg3wVpBi#ToTinYgBc1ULPQZF1Mg2 - Superlokkus

0

很遗憾,但你所做的目前是“最先进的技术”。

另外一些有用的技巧可以在谷歌上搜索:

#pragma init_seg(XXX) - 适用于Windows下的MSVS和Intel C++

__attribute__ ((init_priority(XXX)) - 适用于GCC和clang

此外,我必须警告你,你的本地static技巧不是线程安全的(至少在MSVC、Intel C++、clang和gcc中),因为它是通过检查和设置全局变量来实现的。


非标准解决方案总是便宜的。 - Superlokkus
@MatsPetersson gcc++ 4.7: 17 static C T; => 0x00000000004007ef <+11>: mov $0x600ef0,%eax 0x00000000004007f4 <+16>: movzbl (%rax),%eax 0x00000000004007f7 <+19>: test %al,%al 0x00000000004007f9 <+21>: jne 0x400839 <test(int)+85> - 明显是竞争条件,所以你错了。 - Sergei Kulik
g++ 4.7是支持C++11的最旧版本,可以正确实现守卫。因此,我不确定你所说的gcc 4.x指的是什么。 - M.M
1
你链接到的是 g++ 4.4.7,这是一个 C++11 之前的版本。第一个接受 -std=c++11 的版本是 4.7。但根据 这个表格,g++ 自 4.3 版本以来已经正确实现了它。 - M.M
2
@M.M 我必须承认你是对的。我在关于gcc的结论上过于草率了。即使第一个测试没有受到保护,但它仅用于加速,因为T的构造函数周围有适当的保护。将检查其他编译器。 - Sergei Kulik
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接