调用静态函数时模板化class的静态成员是如何构造的?
How are static members of templated class constructed when a static function is called?
我试图更好地理解模板化 classes / 结构中静态成员的初始化。
让我们来看下面的最小示例(在 Coliru 中可用):
#include <iostream>
struct A {
int value;
A(int val) : value{val} { std::cout << "A(" << value << ")\n"; }
};
struct Static {
static inline A s_a{42};
static void use() {
std::cout << s_a.value++ << '\n';
std::cout << s_a.value << '\n';
}
};
struct User {
User() {
Static::use();
}
};
User s_user{};
int main() {
return 0;
}
输出是(如我所料):
A(42)
42
43
现在,我希望能够为不同的类型提供多个版本的 Static
,因此我将其转换为模板 (Coliru):
#include <iostream>
struct A {
int value;
A(int val) : value{val} { std::cout << "A(" << value << ")\n"; }
};
template<class T>
struct Static {
static inline A s_a{42};
static void use() {
std::cout << s_a.value++ << '\n';
std::cout << s_a.value << '\n';
}
};
struct User {
User() {
Static<int>::use();
}
};
User s_user{};
int main() {
return 0;
}
但是输出是
0
1
A(42)
如您所见,A::value
未初始化使用,A
的构造函数在 Static<int>::use
执行后被调用 .
我可以更改 s_a
的定义,因此它专门用于高级:
template<class T>
struct Static {
static A s_a;
// ...
};
template<> A Static<int>::s_a{42};
并且它工作正常 (Coliru)。对应的是我必须定义每一个专业化,我想让它尽可能容易使用(因为你可以假设实际代码更复杂)。
所以,我的问题是:
为什么 Static<T>::use()
在它 (s_a
) 初始化之前使用 s_a
?我可以考虑某种 延迟初始化,直到专门化 (抱歉,不知道它的正确名称),并且 User<
正在调用一个静态函数,不知何故, 与静态属性无关(只是它们在同一个封装中),但我不知道为什么,也不知道标准对此有何评论。
我应该如何更改此代码以获得与非模板版本相同的行为?也就是说,任何 Static<T>
的属性都可以在 在 调用它们的静态方法之前初始化。
注意:struct A
只是这里的一个最小示例,在我正在处理的项目中,它是一个模板化的 class,它专门基于 Static<T>
.
C++ 中的静态初始化是一场噩梦...C++17 的草案 n4659 在 6.6.3 非局部变量的动态初始化中说 [basic.start.dynamic] §1
Dynamic initialization of a non-local variable with static storage duration is unordered if the variable is an implicitly or explicitly instantiated specialization, is partially-ordered if the variable is an inline variable that is not an implicitly or explicitly instantiated specialization, and otherwise is ordered.
当 Static
class 未模板化时,您有一个部分排序的初始化来保证 Static::s_a
的初始化发生在 s_user
的初始化之前。但是当你使用模板时,初始化变得无序,实现可以选择它想要的。在我的测试中,gcc 10 给出的结果与您得到的结果相同(s_users
在 Static::s_a
之前初始化),但是 clang 12 给出了相反的顺序...
你很幸运使用了gcc并看到了问题。如果您使用 Clang,您将在开发时继续进行,问题可能会在以后发生。更糟糕的是,我不确定显式实例化是否真的保证了初始化顺序。如果我正确理解了标准,我会说不,无论如何,我绝不会依赖它来制作生产代码。
我试图更好地理解模板化 classes / 结构中静态成员的初始化。
让我们来看下面的最小示例(在 Coliru 中可用):
#include <iostream>
struct A {
int value;
A(int val) : value{val} { std::cout << "A(" << value << ")\n"; }
};
struct Static {
static inline A s_a{42};
static void use() {
std::cout << s_a.value++ << '\n';
std::cout << s_a.value << '\n';
}
};
struct User {
User() {
Static::use();
}
};
User s_user{};
int main() {
return 0;
}
输出是(如我所料):
A(42)
42
43
现在,我希望能够为不同的类型提供多个版本的 Static
,因此我将其转换为模板 (Coliru):
#include <iostream>
struct A {
int value;
A(int val) : value{val} { std::cout << "A(" << value << ")\n"; }
};
template<class T>
struct Static {
static inline A s_a{42};
static void use() {
std::cout << s_a.value++ << '\n';
std::cout << s_a.value << '\n';
}
};
struct User {
User() {
Static<int>::use();
}
};
User s_user{};
int main() {
return 0;
}
但是输出是
0
1
A(42)
如您所见,A::value
未初始化使用,A
的构造函数在 Static<int>::use
执行后被调用 .
我可以更改 s_a
的定义,因此它专门用于高级:
template<class T>
struct Static {
static A s_a;
// ...
};
template<> A Static<int>::s_a{42};
并且它工作正常 (Coliru)。对应的是我必须定义每一个专业化,我想让它尽可能容易使用(因为你可以假设实际代码更复杂)。
所以,我的问题是:
为什么
Static<T>::use()
在它 (s_a
) 初始化之前使用s_a
?我可以考虑某种 延迟初始化,直到专门化 (抱歉,不知道它的正确名称),并且User<
正在调用一个静态函数,不知何故, 与静态属性无关(只是它们在同一个封装中),但我不知道为什么,也不知道标准对此有何评论。我应该如何更改此代码以获得与非模板版本相同的行为?也就是说,任何
Static<T>
的属性都可以在 在 调用它们的静态方法之前初始化。
注意:struct A
只是这里的一个最小示例,在我正在处理的项目中,它是一个模板化的 class,它专门基于 Static<T>
.
C++ 中的静态初始化是一场噩梦...C++17 的草案 n4659 在 6.6.3 非局部变量的动态初始化中说 [basic.start.dynamic] §1
Dynamic initialization of a non-local variable with static storage duration is unordered if the variable is an implicitly or explicitly instantiated specialization, is partially-ordered if the variable is an inline variable that is not an implicitly or explicitly instantiated specialization, and otherwise is ordered.
当 Static
class 未模板化时,您有一个部分排序的初始化来保证 Static::s_a
的初始化发生在 s_user
的初始化之前。但是当你使用模板时,初始化变得无序,实现可以选择它想要的。在我的测试中,gcc 10 给出的结果与您得到的结果相同(s_users
在 Static::s_a
之前初始化),但是 clang 12 给出了相反的顺序...
你很幸运使用了gcc并看到了问题。如果您使用 Clang,您将在开发时继续进行,问题可能会在以后发生。更糟糕的是,我不确定显式实例化是否真的保证了初始化顺序。如果我正确理解了标准,我会说不,无论如何,我绝不会依赖它来制作生产代码。