Подтвердить что ты не робот

Шаблон С++: статический член в глобальном объекте не инициализирован

У меня есть кусок простого кода на С++, в котором я определил шаблон и глобальный объект, специализируясь на шаблоне. Конструктор объектов обращается к статическому члену в специализированном шаблоне. Но, оказывается, статический член не инициализируется в этой точке. Но для локального объекта (определенного в теле функции) он работает. Я в замешательстве...

Мой компилятор С++: g++ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.4) 5.4.0 20160609

/////////////////////////
template<typename T>
class TB{
public:
  const char *_name;
  TB(const char * str):_name(str){
    cout << "constructor is called:" << _name << endl;
  };

  virtual ~TB(){
    cout << "destructor is called:" << _name << endl;
  };
};

template<typename T>
class TA{
public:
  const char *_name;
  TA(const char * str):_name(str){
    cout << "constructor is called:" << _name << endl;
    cout << tb._name <<endl;
  };

  virtual ~TA(){
    cout << "destructor is called:" << _name << endl;
  };

  static TB<T> tb;
};

template<typename T>
  TB<T> TA<T>::tb("static-tb");
TA<int> ta("global-ta");

int main(int argc,char ** argv){
  cout << "program started." << endl;
  cout << "program stopped." << endl;
  return 0;
}

/////////////////////////
//  OUTPUT:
constructor is called:global-ta
// yes, only such a single line.

Если я ставлю определение ta в main() следующим образом, оно работает.

int main(int argc,char ** argv){
  cout << "program started." << endl;
  TA<int> ta("local-ta");
  cout << "program stopped." << endl;
  return 0;
}

/////////////////////
//  OUTPUT:
constructor is called:static-tb
program started.
constructor is called:local-ta
static-tb
program stopped.
destructor is called:local-ta
destructor is called:static-tb
// end of output
4b9b3361

Ответ 1

В первом сценарии ваша программа потерпела крах до начала запуска. Он падает внутри конструктора ta, потому что он обращается к tb, который еще не сконструирован. Это форма статического инициализационного заказа Fiasco

Второй сценарий был успешным, потому что ta находится внутри main, и что гарантировано, что tb, который является нелокальным, был построен до ta.

Вопрос в том, почему в первом сценарии ta был построен до tb, хотя tb и ta были определены в одном модуле компиляции с tb, определенным до ta?

Зная, что tb является статическим элементом данных шаблона, эта цитата из cppreference применяется:

Динамическая инициализация

После завершения статической инициализации, динамическая инициализация нелокальных переменных происходит в следующем ситуации:

1) Неупорядоченная динамическая инициализация, которая применяется только к (статическим/поточно-локальным) шаблонам переменных и (начиная с С++ 11) классу static static, которые не являются явно специализированными. Инициализация таких статических переменных неопределенно упорядочена по отношению ко всей другой динамической инициализации, за исключением случаев, когда программа запускает поток до инициализации переменной, и в этом случае его инициализация не подвержена изменениям (начиная с С++ 17). Инициализация таких потоков-локальных переменных не влияет на все остальные динамические инициализации.

Итак, здесь нет последовательности! Поскольку ta является статической переменной с явной спецификацией шаблона, компилятору было разрешено инициализировать его до tb.

В другой цитате с той же страницы говорится:

Ранняя динамическая инициализация

Компиляторам разрешено инициализировать динамически инициализированные переменные как часть статической инициализации (по существу, во время компиляции), если выполняются следующие условия:

1) динамическая версия инициализации не изменяет значение любого другого объекта области пространства имен до его инициализации

2) статическая версия инициализации дает одно и то же значение в инициализированной переменной, как это было бы вызвано динамической инициализацией, если бы все переменные, не требуемые для инициализации статически, были динамически инициализированы. Из-за вышеприведенного правила, если инициализация какого-либо объекта o1 относится к объекту o2 области пространства имен, который потенциально требует динамической инициализации, но определен позже в той же самой системе перевода, не указано, будет ли значение o2 использоваться значением полностью инициализированного o2 (поскольку компилятор способствовал инициализации o2 для компиляции) или будет значением o2, просто инициализированным нулем.

Компилятор решил в соответствии с этими правилами продвигать инициализацию ta до tb. Было ли это объяснено статической инициализацией, но в любом случае кажется довольно очевидным, что последовательность инициализации не гарантируется, когда речь идет о переменных шаблонах и элементах статических шаблонов из-за первой цитаты и правил продвижения вторая цитата.

Решение

Чтобы гарантировать, что tb инициализируется до его использования, самым простым является его размещение внутри функции-обертки. Я думаю, что это должно быть каким-то эмпирическим правилом при работе со статическими членами шаблонов:

template<typename T>
class TA{
    //...
    static TB<T>& getTB();
};

template<typename T>
TB<T>& TA<T>::getTB()
{ static TB<T> tb("static-tb");
  return tb;
}