Инициализация инициализации статического члена без отсрочки для шаблонов в gcc?

Есть ли у gcc какие-либо гарантии относительно статической синхронизации инициализации члена, особенно в отношении классов шаблонов?

Я хочу знать, могу ли я получить твердую гарантию, что статические члены (PWrap_T<T>::p_s) будут инициализированы до 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();
}

(С++ 11 §3.6.2/4 - [basic.start.init]:)

Определяется реализацией, выполняется ли динамическая инициализация нелокальной переменной со статической продолжительностью хранения до первого утверждения main. Если инициализация отложена до некоторого момента времени после первого утверждения main, она должна произойти до первого использования odr (3.2) любой функции или переменной, определенной в той же самой единицы перевода, что и переменная, подлежащая инициализации.

... Нелокальная переменная со статической продолжительностью хранения, имеющая инициализацию с побочными эффектами, должна быть инициализирована, даже если она не используется odr (3.2, 3.7.1).

Кроме того, попытка __attribute__ ((init_priority(int))) или __attribute__ ((constructor)) для инициализации члена шаблона дала warning: attributes after parenthesized initializer ignored, и я не знаю других трюков относительно статической инициализации.

Заранее благодарим любого, кто может дать мне ответ об этом!

Ответ 1

Стандарт гарантирует, что статические объекты продолжительности хранения инициализируются до того, как любые функции/переменные в одной и той же единицы перевода, что и ваш объект, будут использованы из внешнего источника.

Формулировка здесь предназначена для работы с общими библиотеками. Поскольку разделяемые библиотеки могут быть динамически загружены после запуска 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.
  }
}

Ответ 2

Как вы уже узнали, стандарт С++ не гарантирует, что "динамическая инициализация нелокальной переменной со статической продолжительностью хранения будет выполнена до первого утверждения main". Однако GCC выполняет такую ​​инициализацию перед выполнением main, как описано в Как обрабатываются функции инициализации.

Единственная проблема - инициализация статических объектов из разделяемых библиотек, загруженных с помощью dlopen. Они будут инициализироваться только во время загрузки библиотеки, но вы ничего не можете с этим сделать.