gcc是否对静态成员初始化时间有任何保证,特别是关于模板类?
我想知道在多个编译单元实例化类时,是否可以在PWrap_T<T>::p_s
之前初始化静态成员(main()
)。尝试在main开始时手动触摸每个编译单元的符号是不切实际的,但我不清楚其他任何东西是否有效。
我已经使用不同单位的bar()
等方法进行了测试,并且始终获得了所需的结果,但我需要知道何时/如果gcc会将地毯拉出来并且是否是预防的。
此外,在库完成加载之前,是否会初始化DSO中的所有静态成员?
#include <iostream>
#include <deque>
struct P;
inline std::deque<P *> &ps() { static std::deque<P *> d; return d; }
void dump();
struct P {
P(int id, char const *i) : id_(id), inf_(i) { ps().push_back(this); }
void doStuff() { std::cout << id_ << " (" << inf_ << ")" << std::endl; }
int const id_;
char const *const inf_;
};
template <class T>
struct PWrap_T { static P p_s; };
// *** Can I guarantee this is done before main()? ***
template <class T>
P PWrap_T<T>::p_s(T::id(), T::desc());
#define PP(ID, DESC, NAME) /* semicolon must follow! */ \
struct ppdef_##NAME { \
constexpr static int id() { return ID; } \
constexpr static char const *desc() { return DESC; } \
}; \
PWrap_T<ppdef_##NAME> const NAME
// In a compilation unit apart from the template/macro header.
void dump() {
std::cout << "[";
for (P *pp : ps()) { std::cout << " " << pp->id_ << ":" << pp->inf_; }
std::cout << " ]" << std::endl;
}
// In some compilation unit.
void bar(int cnt) {
for (int i = 0; i < cnt; ++i) {
PP(2, "description", pp);
pp.p_s.doStuff();
}
}
int main() {
dump();
PP(3, "another", pp2);
bar(5);
pp2.p_s.doStuff();
}
(C ++11§3.6.2/ 4 - [basic.start.init]:)
实现定义了具有静态存储持续时间的非局部变量的动态初始化是否在main的第一个语句之前完成。 如果初始化延迟到第一个main语句之后的某个时间点,则应该在与要初始化的变量相同的转换单元中定义的任何函数或变量的第一个odr-use(3.2)之前发生。
......具有静态存储持续时间且具有副作用的初始化的非局部变量必须初始化,即使它没有经过使用(3.2,3.7.1)。
此外,尝试__attribute__ ((init_priority(int)))
或__attribute__ ((constructor))
模板成员的初始化产生了warning: attributes after parenthesized initializer ignored
,我知道没有其他关于静态初始化的技巧。
提前感谢能够给我答案的任何人!
答案 0 :(得分:2)
该标准保证在与外部源使用相同的转换单元中的任何函数/变量之前初始化静态存储持续时间对象。
此处的措辞旨在与共享库一起使用。因为在main()启动后可以动态加载共享库,所以语言规范必须足够灵活以应对它。但只要您从翻译单元外部访问您的对象,那么您可以保证在您获得访问权限之前构建它(除非您做了一些病态的事情)。
但如果在同一个编译单元中的另一个静态存储持续时间对象的构造函数中使用它,则不会在初始化之前停止使用它。
但是,您可以使用下面的技术轻松地手动提供静态对象在使用之前正确初始化的保证。
只需将静态变量更改为静态函数即可。然后在函数内部声明一个返回的静态成员。因此,您可以像以前一样使用它(只需添加()
)。
template <class T>
struct PWrap_T
{
static P& p_s(); // change static variable to static member function.
// Rather than the type being P make it a reference to P
// because the object will be held internally to the function
};
template <class T>
P& PWrap_T<T>::p_s()
{
// Notice the member is static.
// This means it will live longer than the function.
// Also it will be initialized on first use.
// and destructed when other static storage duration objects are destroyed.
static P p_s_item(T::id(), T::desc());
return p_s_item;
// Note its not guaranteed to be created before main().
// But it is guaranteed to be created before first use.
}
所以在这里你可以获得全球化的好处(无论它们是什么)。您可以保证构造/销毁,并且您知道在使用之前对象将被正确构建。
您需要做的唯一改变是:
void bar(int cnt) {
for (int i = 0; i < cnt; ++i) {
PP(2, "description", pp);
pp.p_s().doStuff();
// ^^ Add the braces here.
}
}
答案 1 :(得分:1)
正如您已经发现的那样,C ++标准并不能保证“在第一个main语句之前完成具有静态存储持续时间的非局部变量的动态初始化”。但是,GCC确实在执行main之前执行此类初始化,如How Initialization Functions Are Handled中所述。
唯一的问题是从加载了dlopen
的共享库中初始化静态对象。这些只会在加载库时初始化,但是你无能为力。