Еще один способ решения Static Initialization Order Fiasco

#опытным



Предыдущий пост навел меня на еще один метод решения SIOF. Это в догонку к этому посту с решениями.



Суть в чем. Как верно указал наш подписчик xiran в этом комментарии - управлять временем жизни глобальных динамически созданных объектов намного проще, чем временем жизни статиков. Поэтому можно объявить не статические переменные, а статические указатели. Указатель можно инициализировать nullptr и оставить его в таком состоянии хоть на месяц. И вы можете его инициализировать в любой подходящий для вас момент времени.



Это позволит вам в одном месте инициализировать связанные объекты сразу и в том порядке, в котором это не вызовет неприятных эффектов. Вы полностью контролируете ситуацию.



// header.hpp

struct Class {

Class(int num) : field{num} {}

int field;

};



// source.cpp

Class * static_ptr2 = nullptr;



//main.cpp

int * static_ptr1;

extern Class * static_ptr2;



void Init() {

static_ptr1 = new int{6};

static_ptr2 = new Class{*static_ptr1};

}



int main() {

Init();

std::cout << static_ptr2->field << std::endl;

}



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



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



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



// header.hpp

struct Class {

Class(int num) : field{num} {}

int field;

};



// source.cpp

std::unique_ptr<Class> static_ptr2 = nullptr;



//main.cpp

std::unique_ptr<int> static_ptr1 = nullptr;

extern std::unique_ptr<Class> static_ptr2;



void Init() {

static_ptr1 = std::make_unique<int>(6);

static_ptr2 = std::make_unique<Class>(*static_ptr1);

}



int main() {

Init();

std::cout << static_ptr2->field << std::endl;

}



Вот так это выглядит в "идеале". Можете дальше пользоваться своими глобальными переменными(осуждаем), но хотя бы безопасно.



Stay safe. Stay cool.



#cpprore #cpp11 #STL #pattern