Решение static initialization order fiasco



Раз есть проблема - должно быть и решение. Сегодня поговорим о паре-тройке вариантов. Пост вдохновлен этим комментом нашего подписчика Антона.



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



1️⃣ Самое очевидное - дропнуть дурнопахнущие статики. Ну или стараться по-максимуму уменьшать их количество. Человечество давно осознало, что глобальные переменные - зло, а со злом нужно бороться и побеждать его. Используйте ООП, группируйте данные вместе. И не ленитесь передавать объекты в функции. И это поможет вам избавиться о большинства глобальных переменных. Способ, я бы сказал, идеальный. Но наш мир таковым не является и в реальном коде будут продолжать жить статики и надо уметь с ними правильно обходиться.



2️⃣ Делайте свои глобальные объекты constexpr. Глобальные изменяемые объекты - зло. Но вот умные константы, для которых можно проводить вычисления на этапе компиляции - тема богоугодная. Константны в коде так или иначе нужны, а в современных стандартах много уделяется внимания вычислениям на этапе компиляции и не зря. Вряд ли вам на этапе инициализации программы нужно делать что-то суперсложное, зависящее от внешнего мира. Зачастую, много чего можно вычислить в compile-time и не заботиться об опасностях динамической инициализации. К тому же их инициализация безопасна и предсказуема.



3️⃣ Иметь один хэдэр со всеми глобальными переменными и определить их все в одной единице трансляции. В пределах единицы трансляции порядок полностью определен, поэтому никаких проблем не будет. Однако есть один момент, что это решение будет сильно связывать друг с другом несвязанный по смыслу код. Держать все переменные в одном месте может показаться удобным на первый взгляд. Это еще сильнее развязывает руки разработчикам в плане увеличения количества связей между переменными. И в будущем распутывать эти связи будет еще сложнее. Single responsibility ушел в закат...

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

Способ хоть и рабочий и много где используется, но далеко не идеальный.



4️⃣ Construct on first use idiom. Помните, как мы говорили про то, что статические локальные переменные функций инициализируются при первом вызове функции? Так вот эту особенность можно использовать, чтобы никогда не использовать объект в неинициализированном виде. Если у вас есть переменная А, инициализация которой зависит от переменной В, то есть вероятность, что В еще не инициализирована. Тогда можно переменную В обернуть в глобальную функцию-геттер, в которой эта переменная будет хранится в виде статическом локальной переменной и ее значение будет возвращаться наружу. Таким образом любое использование переменной будет проходить через вызов этой функции и нам гарантируется, что переменная создастся в момент первого вызова функции. Техника заслуживает отдельный пост, который выйдет чуть позже.



Solve your problems. Stay cool.



#cppcore