【问题标题】:Initialization of static members of class templates with side effects初始化具有副作用的类模板的静态成员
【发布时间】:2017-11-11 18:40:21
【问题描述】:

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

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

    // do some work

    return 42;
}

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

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

我可以在编译时创建对象名称列表吗?我非常欣赏 Alexanderscu 先生的方法,所以我想,“当然,为什么不呢,我将制作一个非常聪明的用户定义字符串文字并收工”。

想法如下:

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

用户定义的字符串字面量应该大致如下应用:

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

    // do some work

    return 42;
}

看到_probe?这应该是解决方案。

好吧,它没有用。事实证明,编译器将 dummy static 成员 的初始化推迟到实际调用用户定义的字符串文字的位置。这意味着在每个命名实体至少创建一次之前,我的名称列表将保持不完整。

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

延迟动态初始化

动态初始化是发生在主函数的第一条语句之前(对于静态)还是线程的初始函数(对于线程局部变量),或者推迟到之后发生,这是由实现定义的。

如果非内联变量的初始化延迟发生在 main/thread 函数的第一条语句之后,它发生在任何变量的第一次 odr-use 之前,该变量在同一翻译单元中定义为静态/线程存储持续时间要初始化的变量。如果给定的翻译单元没有使用任何变量或函数,则该翻译单元中定义的非局部变量可能永远不会被初始化(这模拟了按需动态库的行为)。 但是,只要 TU 中的任何东西被 odr-used,所有初始化或销毁有副作用的非局部变量都将被初始化,即使它们没有在程序中使用。

您会在下面发现 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。为什么编译器不想初始化我的静态变量?这种行为正确吗?我该如何解决这个问题?

【问题讨论】:

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


    【解决方案1】:

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

    static int run()
    {
        &dummy_;
    

    Run in online compiler

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

    【讨论】:

    • 谢谢,它成功了。是否保证即使启用优化也不会消除副作用?我担心编译器可能会推断出表达式(void) &amp;dummy_ 无效,因此可以将其消除,这会破坏我的代码。
    • 也感谢有关 GNU 扩展的警告。我最终使用了一种稍微不同的方法,但就这个问题而言,主要概念没有改变——我仍然必须依赖副作用。
    猜你喜欢
    • 1970-01-01
    • 2016-03-10
    • 1970-01-01
    • 1970-01-01
    • 2011-03-14
    • 2011-01-21
    • 2011-01-19
    • 2011-03-19
    相关资源
    最近更新 更多