对象被使用之前先被初始化--定义于不同编译单元内的non-local tatic对象

时间:2021-08-19 19:48:34

涉及到至少两个源码文件,每一个至少一个non-local static对象。真正的问题是:如果某个编译单元内的某个non-local static对象的初始化动作使用了另一个编译单元内的某个non-local static对象,它所用到的这个对象可能尚未初始化,因为C++对定义于不同编译单元内的non-local static对象的初始化次序并无明确定义。

假设你有一个FileSystem class,它让互联网上的文件看起来好像位于本地(loacal)。由于这个class使世界看起来像个单一文件系统,你可能会产生一个特殊对象,位于global或namespace作用域内,象征单一文件系统。

class FileSystem{
public:
...
std::size_t numDisks() const; //众多成员函数之一
...
};
extern FileSystem tfs; //预备给客户使用的对象;
假设某些客户建立了一个class用以处理文件系统内的目录(director).很自然,他们的class会用上FileSystem对象

class Directory{
public:
Directory(params);
...
};
Directory::Directory(params){
...
std::size_t disks = tfs.numDisks();//使用tfs对象
}
//进一步假设,这些客户决定创建一个Directory对象,用来放置临时文件Directory tempDir(params);
现在,初始化的次序重要性显示出来了:除非tfs在temDir之前先被初始化,否则temDir的构造函数会用到尚未初始化的tfs。

可以使用单例模式完全消除这个问题。将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个引用指向它所包含的对象。然后用户调用这些函数。而不直接指涉这些对象。换句话说,non-local static对象被local static对象替换了。

基础在于C++保证,函数内的local static对象会在该函数被调用期间首次遇上该对象的定义式时被初始化。

class FileSystem{...};
FileSystem& tfs(){ //这个函数用来替换tfs对象;它在FileSystem class中可能是一个static。定义并初始化一个
static FileSystem fs;   //local static 对象,返回一个指向fs的引用。return fs;}class Directory{...};Directory::Directory(params){...std::size_t disks = tfs().numDisks(); //原本指向tfs的引用现在改为tfs()...}Directory& tempDir()      //这个函数用来替换tempDir对象;它在Directory class中可能是个static,定义并初始化
{static Directory td;  // local static对象,返回一个指向td对象的一个引用。return td;}
这种结构下的reference-returning函数往往十分的单纯:第一行定义并初始化一个local static对象,第二行就返回它。这样的单纯性使它们成为绝佳的inlining函数,尤其是频繁调用它们。

运用reference-returning函数防止“初始化次序问题”,前提是其中有着一个对对象而言合理的初始化次序。

为避免在对象初始化之前过早的使用它们,需要做的三件事:第一,手工初始化内置型non-member对象。第二是用成员初始化列表对付对象的所有成分。最后,在初始化次序不确定性(这对不同编译单元所定义的non-local static对象来说是一种折磨)氛围下加强你的设计。