Есть ли у 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, это должно произойти до первого использования (3.2) какой-либо функции или переменной, определенной в той же единице перевода, что и для инициализируемой переменной.
… Нелокальная переменная со статической продолжительностью хранения, имеющая инициализацию с побочными эффектами, должна быть инициализирована, даже если она не используется odr (3.2, 3.7.1).
Кроме того, пытаясь __attribute__ ((init_priority(int)))
или же __attribute__ ((constructor))
для инициализации члена шаблона получен warning: attributes after parenthesized initializer ignored
и я не знаю других приемов, касающихся статической инициализации.
Заранее спасибо всем, кто может дать мне ответ по этому поводу!
Стандарт гарантирует, что статические объекты продолжительности хранения инициализируются до того, как какие-либо функции / переменные в той же единице перевода, что и ваш объект, будут использованы из внешнего источника.
Формулировка здесь предназначена для работы с общими библиотеками. Поскольку разделяемые библиотеки могут быть динамически загружены после запуска 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.
}
}
Как вы уже узнали, стандарт C ++ не гарантирует, что «динамическая инициализация нелокальной переменной со статической продолжительностью хранения выполняется до первого оператора main». Однако GCC выполняет такую инициализацию перед выполнением main, как описано в Как обрабатываются функции инициализации.
Единственная проблема — инициализация статических объектов из общих библиотек, загруженных dlopen
, Они будут инициализированы только во время загрузки библиотеки, но вы ничего не можете с этим поделать.