初始化具有副作用的类模板的静态成员

时间:2017-11-11 18:40:21

标签: c++ templates static c++14 static-initialization

My C ++ 14应用程序需要动态创建和销毁某种类型的对象。这些对象中的每一个都被命名,并且在构造对象时分配名称。每个名称都被硬编码为字符串文字。以下伪代码演示了这个想法:

int foo()
{
    NamedEntity entity1("Bar");
    NamedEntity entity2("Baz");

    // do some work

    return 42;
}

我的目标是创建应用程序中使用的所有对象名称的常量列表,并使其在运行时可供应用程序访问。

第一个想到的天真解决方案是grep源并使用硬编码的名称列表自动生成标头。让我们把这个解决方案放在一边作为最后的选择。

我可以在编译时创建对象名称的列表吗?我是Alexanderscu先生的方式的崇拜者,所以我想,“当然,为什么不,我要制作一个非常聪明的用户定义的字符串文字并称之为一天”。

这个想法如下:

  1. 使用用户定义的字符串文字为每个对象名称实例化一个新类型。我们将此类型称为对象名称类型
  2. 为每个实例化的对象名称类型配备虚拟静态成员。使用任意静态函数的返回值初始化所述静态成员。我们将把这个函数称为 registrator函数
  3. 使用对象名称列表定义单例。使用上面提到的 registrator函数将名称附加到单例列表中。
  4. 用户定义的字符串文字大致应用如下:

    int foo()
    {
        NamedEntity entity1("Bar"_probe);
        NamedEntity entity2("Baz"_probe);
    
        // do some work
    
        return 42;
    }
    

    请参阅_probe?这应该是解决方案。

    嗯,它不起作用。事实证明,编译器将虚拟静态成员的初始化推迟到实际调用用户定义的字符串文字的位置。这意味着我的名字列表将保持不完整,直到每个命名实体至少创建一次。

    为什么会这样?显然, registrator函数有副作用(我们只需要它的副作用),因此,根据cppreference,虚拟静态成员的初始化不能被推迟:< / p>

      

    延迟动态初始化

         

    动态初始化是在主函数的第一个语句(用于静态)还是在线程的初始函数(用于线程本地)之前发生的,或者是在之后发生的延迟发生的,是实现定义的。

         

    如果非内联变量的初始化延迟发生在主/线程函数的第一个语句之后,则在第一次使用任何变量之前发生,其中静态/线程存储持续时间在同一个翻译单元中定义为要初始化的变量。如果没有变量或函数来自给定的转换单元,则该转换单元中定义的非局部变量可能永远不会被初始化(这模拟了按需动态库的行为)。 但是,只要TU中的任何内容都被使用,所有初始化或破坏都有副作用的非局部变量即使未在程序中使用也会被初始化。

    下面你会发现一个稍微减少的MWE:

    #include <iostream>
    
    int g_foo = 0;
    
    template <typename T, T... Chars>
    class Registrator
    {
        static int dummy_;
    
    public:
        static int run()
        {
            g_foo++;
            static constexpr char str[sizeof...(Chars) + 1] = { Chars..., '\0' };
            std::cout << "Registering: " << &str[0] << std::endl;
            return g_foo;
        }
    };
    
    template <typename T, T... Chars>
    int Registrator<T, Chars...>::dummy_ = Registrator<T, Chars...>::run();
    
    template <typename T, T... Chars>
    inline int operator""_probe()
    {
        static constexpr char str[sizeof...(Chars) + 1] = { Chars..., '\0' };
        return Registrator<T, Chars...>::run();
    }
    
    int main(int argc, char**)
    {
        std::cout << "g_foo=" << g_foo << std::endl;
    
        if (argc > 1)
        {
            std::cout << "Hello"_probe << std::endl;
            std::cout << "World"_probe << std::endl;
        }
    
        std::cout << "g_foo=" << g_foo << std::endl;
    
        return 0;
    }
    

    如果它能够正常工作,你可以在没有参数的情况下运行它时大致观察到以下输出:

    Registering: Hello
    Registering: World
    g_foo=2
    1
    2
    g_foo=2
    

    但是,我正在观察以下内容:

    g_foo=0
    g_foo=0
    

    这意味着编译器根本没有初始化虚拟静态成员

    使用至少一个参数运行程序将强制它显式使用用户定义的文字,在这种情况下,虚拟静态实际上已初始化,但初始化被推迟到使用用户定义的文字的点:

    g_foo=0
    Registering: Hello
    1
    Registering: World
    2
    g_foo=2
    

    我正在使用GCC 5.4.0和-std=c++14。为什么编译器不想初始化我的静态?这种行为是否正确?我该如何解决这个问题?

1 个答案:

答案 0 :(得分:2)

您需要以某种方式实际使用dummy_,例如通过获取其地址:

static int run()
{
    &dummy_;

Run in online compiler

另请注意,字符串文字运算符模板是GNU扩展。一般而言,自我注册可能不是一个好主意。