如何强制依赖静态对象(包括模板成员)的初始化顺序

How to enforce the initialization order of dependant static objects including template members?

本文关键字:成员 初始化 顺序 包括模 依赖 何强制 静态 对象      更新时间:2023-10-16

我们正在设计一些"类似函数"的数据结构,其中每个对象都是不可变的。为了表示每个容器的空元素,我们决定使用一个静态实例。

我们所做的是在一个文件中定义所有这些静态对象,因此我们可以控制定义的顺序。该头文件包含在主实现文件中。

// StaticInit.h
FunctionalMap FunctionalMap::gEmpty = FunctionalMap();

模板化的类和静态对象

当我们实现一些模板结构时会出现复杂情况:

// FunctionalArray.h
template <class T_contained>
class FunctionalArray
{
    static FunctionalArray gEmpty;
    private:
        FunctionalArray();
}
template <class T_contained>
FunctionalArray<T_contained> FunctionalArray<T_contained>::gEmpty = FunctionalArray();

标准

从标准(和这个答案)

类模板的[…]静态数据成员应在隐式实例化它的每个翻译单元[…],除非相应的专门化被显式实例化[…].

由于我们希望避免每个静态对象的显式实例化,因此我们似乎被迫在头文件中保留静态FunctionalArray::gEmpty的(模板化的)定义(我们可以保证该定义将存在于实例化FunctionalArray的所有转换单元中的唯一方法)。

问题

现在,我们有一个(非模板化的)静态对象,它的初始化使用模板化静态对象的实例。

// StaticInit.h
#include "FunctionalArray.h"
DependantClass DependantClass::gEmpty = methodReferingToTheEmptyArray();

通过包含FunctionalArray.h(在这里定义了空数组),我们本可以预期不会被静态初始化顺序问题所困扰。。。我们错得再离谱不过了!

问题

  • 为什么在我们的案例中没有具体说明订单?(我想这可能是因为编译器仍然只生成空数组的一个实际定义,而这个定义可能在任何其他使用它的编译单元中。但猜测不是很令人满意…)
  • 在我们的案例中,是否有一种方法可以指定初始化的顺序,仍然使用"合并定义文件"方法

这里有一个建议,您可能会发现它对解决问题很有用。我使用这种技术通过将静态信息封装在非静态成员中来避免多次实例化。根据标准,在成员函数中定义的静态变量将在首次调用该成员时实例化一次,也请参阅此处的相关问题。由于静态是在调用包含的成员函数后初始化的,因此可以在运行时完全控制它们的初始化。

请注意,这个代码片段中使用的所有类型都在另一个文件中进行了typedef'd,但我相信它们的真实类型是显而易见的。

// type to name mapper template
template<typename T>
struct TP
{
    struct info
    {
        info() : pT(0), sz(0) { }
        Char *pT;
        UInt sz;
        Char *name() { return pT; }
        UInt size() { return sz; }
    } _info;
    TP() { }
    TP(const Char *pName) { typeInfo(pName); }
    info &typeInfo(const Char *pN=0) 
    { 
        static TP<T> x; 
        if (x._info.pT==0) { x._info.pT = (Char *)pN; x._info.sz=sizeof(T);/* prin tf("==New Type %sn", pN);*/}
//      Assert(x._info.pT !=0 || "TP::typeInfo encountered an unknown type" == 0);
        return x._info; 
    }
};
struct InitBarrayTypes
{
    InitBarrayTypes()
    {
        TP<Char> ctp("char");
        TP<Int> itp("int");
        TP<Long> ltp("long");
        TP<Double> ftp("float");
    }
};
// this will be created many times, but has no data and so takes no space,
// and finally only the static members of TP within typeInfo() will remain
static InitBarrayTypes s__ibt;

以上内容如下所示:

cout << "Name of Int is " << TP<Int>().typeInfo().name() << endl;

我希望你觉得这个技巧有用。