static initialization order fiasco
static initialization order fiasco是对C++的一个非常微妙的并且常见的误解。不幸的是,错误发生在main()开始之前,很难检测到。
简而言之,假设你有存在于不同的源文件x.cpp 和y.cpp的两个静态对象x 和 y。再假定y对象的构造函数会调用x对象的某些方法。
就是这些。就这么简单。
结局是你完蛋不完蛋的机会是50%-50%。如果碰巧x.cpp的编辑单元先被初始化,这很好。但如果y.cpp的编辑单元先被初始化,然后y的构造函数比x的构造函数先运行。也就是说,y的构造函数会调用x对象的方法,而x对象还没有被构造。
如何防止“static initialization order fiasco”?
使用“首次使用时构造(construct on first use)”法,意思就是简单地将静态对象包裹于函数内部。
例如,假设你有两个类,Fred 和 Barney。有一个称为x的全局Fred对象,和一个称为y的全局Barney对象。Barney的构造函数调用了x对象的goBowling()方法。 x.cpp文件定义了x对象:
// File x.cpp
#include "Fred.hpp"
Fred x;
y.cpp文件定义了y对象:
// File y.cpp
#include "Barney.hpp"
Barney y;
Barney构造函数的全部看起来可能是象这样的:
// File Barney.cpp
#include "Barney.hpp"
Barney::Barney()
{
// ...
x.goBowling();
// ...
}
正如以上所描述的,由于它们位于不同的源文件,那么 y 在 x 之前构造而发生灾难的机率是50%。
这个问题有许多解决方案,但一个非常简便的方案就是用一个返回Fred对象引用的全局函数x(),来取代全局的Fred对象 x。
// File x.cpp
#include "Fred.hpp"
Fred& x()
{
static Fred* ans = new Fred();
return *ans;
}
由于静态局部对象只在控制流第一次越过它们的声明时构造,因此以上的new Fred()语句只会执行一次:x()被第一次调用时。每个后续的调用将返回同一个Fred对象(ans指向的那个)。然后你所要做的就是将 x 改成 x():
// File Barney.cpp
#include "Barney.hpp"
Barney::Barney()
{
// ...
x().goBowling();
// ...
}
由于该全局的Fred对象在首次使用时被构造,因此被称为首次使用时构造法(Construct On First Use Idiom)
这种方法的不利方面是Fred对象不会被析构。C++ FAQ Book有另一种技巧消除这个影响(但面临了“static de-initialization order fiasco”的代价)。