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



После статической инициализации в компайлтайме идет динамическая инициализация в рантайме. Хотелось бы сказать, что хоть здесь простой и понятный порядок, но нет. Это глобальные переменные и С++, поэтому будет немного больно.



Динамическая инициализация разделяется на 3 подгруппы:



1️⃣ Неупорядоченная динамическая инициализация. Она применяется только для статических полей шаблонных классов и шаблонных переменных, которые не специализированы явно(явно специализированные шаблоны - обычные классы). И вот порядок установки значений этих сущностей вообще неопределен. Куда понравится компилятору, туда и вставит.



2️⃣ Частично упорядоченная инициализация. Применяется для всех нешаблонных инлайн переменных. Есть 2 переменные: inline переменная А и В, которая не подходит под критерии применения первой подгруппы. Если А определена во всех единицах трансляции раньше В, то и ее инициализация происходит раньше. Здесь есть одна на*бка особенность, которую мы увидим в примере.



3️⃣ Упорядоченная инициализация. Вот это то, что мы упоминали тут. Все переменные со static storage duration, которые не подходят под предыдущие подгруппы, инициализируются в порядке появления их определения в единице трансляции. Между разными единицами трансляции порядок инициализации не установлен.



Давайте на "простой" пример посмотрим:



struct ShowOrderHelper {

ShowOrderHelper(int num) : data{num} {

std::cout << "Object initialized with data " << num << std::endl;

}

int data;

};



static ShowOrderHelper static_var1{3};

static ShowOrderHelper static_var2{4};



struct ClassWithInlineStaticVar {

static inline ShowOrderHelper inline_member{1};

};



inline ShowOrderHelper inline_var{2};



template <class T>

struct TemplateClassWithStaticVar {

static ShowOrderHelper static_member;

};



template <class T>

ShowOrderHelper TemplateClassWithStaticVar<T>::static_member{27};





Возможный вывод:



Object initialized with data 1

Object initialized with data 2

Object initialized with data 27

Object initialized with data 3

Object initialized with data 4





Здесь как раз все три типа проявляются. static_member - статическое поле неспециализированного явно шаблона, поэтому установка ее значения в рандомном месте происходит.



Далее мы имеем уже упорядоченные вещи. inline_member определен раньше, чем inline_var, поэтому она и инициализируется раньше.



Это понятно. Но погодите: inline_member и inline_var определены позже статиков static_var1 и static_var2. Какого хера они инициализирутся раньше? Это же противоречит правилам частично упорядоченной динамической инициализации!



Вот тут-то и кроется подвох: вы наверное подумали, что из факта "если А определено раньше В, то А инициализируется раньше" автоматически вытекает, что в обратном случае А инициализируется позже. Тут вас и подловили: не вытекает. Поэтому она и называется частично упорядоченной инициализацией. В обратном случае порядок неопределен.



Теперь все понятно: inline_member инициализируется строго раньше inline_var, потому что определение стоит раньше. Но, как группа inline'ов, они расположены после static_var1 и static_var2 и в этом случае для них значение устанавливается в неизвестном порядке. В данном случае перед всеми инициализациями.



Ну и статики static_var1 и static_var2 инициализируются в ожидаемом порядке из-за применения упорядоченной инициализации.



И теперь представьте свое лицо, когда вы сделали эти переменные зависимыми друг от друга, предполагая, что статики(со static storage duration) в одной единице трансляции инициализируются в порядке появления определения. Как минимум 🗿, а как максимум🤡.



Последние несколько постов по статикам так и наровят крикнуть: "Не используйте глобальные переменные!" Ну или хотя бы старайтесь не делать их зависимыми друг от друга. Потому что с порядком полный беспорядок, а с перекрестными зависимостями остается только надеяться, что заговор бабки-поветухи на продуктивную работу поможет не словить багов.



Decouple your program. Stay cool.



#cppcore #cpp17