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”的代价)。
posted @ 2009-11-09 02:47  史莱姆  阅读(654)  评论(0编辑  收藏  举报