【发布时间】:2017-11-11 18:40:21
【问题描述】:
我的 C++14 应用程序需要动态创建和销毁某种类型的对象。这些对象中的每一个都被命名,并且在构造对象时分配名称。每个名称都被硬编码为字符串文字。下面的伪代码演示了这个想法:
int foo()
{
NamedEntity entity1("Bar");
NamedEntity entity2("Baz");
// do some work
return 42;
}
我的目标是创建应用程序中使用的所有对象名称的常量列表,并使其在运行时可供应用程序访问。
想到的第一个天真的解决方案是 grep 源并使用硬编码的名称列表自动生成标题。让我们把这个解决方案放在一边,作为最后的选择。
我可以在编译时创建对象名称列表吗?我非常欣赏 Alexanderscu 先生的方法,所以我想,“当然,为什么不呢,我将制作一个非常聪明的用户定义字符串文字并收工”。
想法如下:
- 使用用户定义的字符串文字为每个 对象名称 实例化一个新类型。我们将把这种类型称为对象名称类型。
- 为每个实例化的对象名称类型配备一个虚拟静态成员。使用任意静态函数的返回值初始化所述静态成员。我们将把这个函数称为注册函数。
- 使用对象名称列表定义一个单例。使用上面提到的 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