具有副作用的 class 模板静态成员的初始化
Initialization of static members of class templates with side effects
我的 C++14 应用程序需要动态创建和销毁特定类型的 object。这些 object 中的每一个都被命名,并且在构造 object 时分配名称。每个名称都是 hard-coded 作为字符串文字。以下伪代码演示了这个想法:
int foo()
{
NamedEntity entity1("Bar");
NamedEntity entity2("Baz");
// do some work
return 42;
}
我的objective是为应用程序中使用的所有object名称创建一个常量列表,并让应用程序可以访问运行次。
想到的第一个天真的解决方案是 grep 源并自动生成 header 和 hard-coded 名称列表。让我们把这个解决方案放在一边作为最后的选择。
我可以在编译时创建 object 名称 的列表吗?我非常钦佩 Alexanderscu 先生的方式,所以我想,"Sure, why not, I'm going to craft a really clever user-defined string literal and call it a day"。
当时的想法如下:
- 使用 user-defined 字符串文字为每个 object 名称 实例化一个新类型。我们将此类型称为 object 名称类型 .
- 为每个实例化的 object 名称类型 配备一个虚拟静态成员。使用任意静态函数的 return 值初始化所述静态成员。我们将此函数称为 注册器函数。
- 使用 object 个名称 的列表定义单例。使用上面提到的注册函数将名称附加到单例列表。
user-defined 字符串文字应该大致应用如下:
int foo()
{
NamedEntity entity1("Bar"_probe);
NamedEntity entity2("Baz"_probe);
// do some work
return 42;
}
看到_probe
了吗?这应该是解决方案。
嗯,没用。事实证明,编译器将 虚拟静态成员 的初始化推迟到实际调用 user-defined 字符串文字的位置。这意味着我的名称列表将保持不完整,直到每个命名实体至少创建一次。
为什么会这样?显然 registrator 函数 有副作用(我们需要它只是为了它的副作用),因此,根据 cppreference,虚拟静态成员的初始化不能推迟:
Deferred dynamic initialization
It is implementation-defined whether dynamic initialization happens-before the first statement of the main function (for statics) or the initial function of the thread (for thread-locals), or deferred to happen after.
If the initialization of a non-inline variable is deferred to happen after the first statement of main/thread function, it happens before the first odr-use of any variable with static/thread storage duration defined in the same translation unit as the variable to be initialized. If no variable or function is odr-used from a given translation unit, the non-local variables defined in that translation unit may never be initialized (this models the behavior of an on-demand dynamic library). However, as long as anything from a TU is odr-used, all non-local variables whose initialization or destruction has side effects will be initialized even if they are not used in the program.
下面您会发现 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..., '[=13=]' };
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..., '[=13=]' };
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
这意味着编译器根本没有初始化虚拟静态成员。
运行 至少有一个参数的程序将强制它显式使用 user-defined 文字,在这种情况下,虚拟静态实际上得到了初始化,但初始化被推迟到使用 user-defined 文字的地方:
g_foo=0
Registering: Hello
1
Registering: World
2
g_foo=2
我在 -std=c++14
中使用 GCC 5.4.0。为什么编译器不想初始化我的静态变量?这种行为是否正确?我该如何解决这个问题?
您需要以某种方式实际使用 dummy_
,例如获取其地址:
static int run()
{
&dummy_;
另请注意,字符串文字运算符模板是 GNU 扩展。一般来说,自行注册可能不是一个好主意。
我的 C++14 应用程序需要动态创建和销毁特定类型的 object。这些 object 中的每一个都被命名,并且在构造 object 时分配名称。每个名称都是 hard-coded 作为字符串文字。以下伪代码演示了这个想法:
int foo()
{
NamedEntity entity1("Bar");
NamedEntity entity2("Baz");
// do some work
return 42;
}
我的objective是为应用程序中使用的所有object名称创建一个常量列表,并让应用程序可以访问运行次。
想到的第一个天真的解决方案是 grep 源并自动生成 header 和 hard-coded 名称列表。让我们把这个解决方案放在一边作为最后的选择。
我可以在编译时创建 object 名称 的列表吗?我非常钦佩 Alexanderscu 先生的方式,所以我想,"Sure, why not, I'm going to craft a really clever user-defined string literal and call it a day"。
当时的想法如下:
- 使用 user-defined 字符串文字为每个 object 名称 实例化一个新类型。我们将此类型称为 object 名称类型 .
- 为每个实例化的 object 名称类型 配备一个虚拟静态成员。使用任意静态函数的 return 值初始化所述静态成员。我们将此函数称为 注册器函数。
- 使用 object 个名称 的列表定义单例。使用上面提到的注册函数将名称附加到单例列表。
user-defined 字符串文字应该大致应用如下:
int foo()
{
NamedEntity entity1("Bar"_probe);
NamedEntity entity2("Baz"_probe);
// do some work
return 42;
}
看到_probe
了吗?这应该是解决方案。
嗯,没用。事实证明,编译器将 虚拟静态成员 的初始化推迟到实际调用 user-defined 字符串文字的位置。这意味着我的名称列表将保持不完整,直到每个命名实体至少创建一次。
为什么会这样?显然 registrator 函数 有副作用(我们需要它只是为了它的副作用),因此,根据 cppreference,虚拟静态成员的初始化不能推迟:
Deferred dynamic initialization
It is implementation-defined whether dynamic initialization happens-before the first statement of the main function (for statics) or the initial function of the thread (for thread-locals), or deferred to happen after.
If the initialization of a non-inline variable is deferred to happen after the first statement of main/thread function, it happens before the first odr-use of any variable with static/thread storage duration defined in the same translation unit as the variable to be initialized. If no variable or function is odr-used from a given translation unit, the non-local variables defined in that translation unit may never be initialized (this models the behavior of an on-demand dynamic library). However, as long as anything from a TU is odr-used, all non-local variables whose initialization or destruction has side effects will be initialized even if they are not used in the program.
下面您会发现 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..., '[=13=]' };
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..., '[=13=]' };
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
这意味着编译器根本没有初始化虚拟静态成员。
运行 至少有一个参数的程序将强制它显式使用 user-defined 文字,在这种情况下,虚拟静态实际上得到了初始化,但初始化被推迟到使用 user-defined 文字的地方:
g_foo=0
Registering: Hello
1
Registering: World
2
g_foo=2
我在 -std=c++14
中使用 GCC 5.4.0。为什么编译器不想初始化我的静态变量?这种行为是否正确?我该如何解决这个问题?
您需要以某种方式实际使用 dummy_
,例如获取其地址:
static int run()
{
&dummy_;
另请注意,字符串文字运算符模板是 GNU 扩展。一般来说,自行注册可能不是一个好主意。