Ответ 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;
}