Инициализация статических полей класса. Ч2
Как думаете, может ли быть такое, что статическое поле класса инициализируется после завершения вызова конструктора класса? То есть объект уже создался, а статическое поле его класса еще не инициализированно? Подумайте пару секунд над этим вопросом самостоятельно.
После вчерашнего поста вам уже немного легче должны были даться рассуждения. Загвоздка в том, что статическое объекты инициализируются в порядке определения и не важно, какого класса эти объекты.
Посмотрим на пример.
Есть у нас класс, который сохраняет все свои инстансы по ключу в статическую мапу и все созданные инстансы доступны только через эту мапу.
Чтобы такое провернуть, создаем в классе статическую мапу, статический метод Create, который предоставляет доступ к созданию объектов и объявляем конструктор класса приватным, чтобы никто снаружи не смог втихаря создать объект. Таким образом, доступ к объектам есть только через метод Create и статическую мапу.
Не имея представления о нюансах инициализации, взбрело нам в голову создать и использовать объект перед определением мапы.
Запуская все это дело, получим сегфолт. И да, да, вы все правильно поняли. Все из-за порядка инициализации.
Так как линкеру пофиг на тип статических объектов, он спокойно может поставить инициализацию статического поля класса после завершения работы конструктора объекта в глобальном неймспейсе. Вот и получается конфуз: объект надо создавать, а используемое поле не инициализировано. От того и падаем.
Здесь ситуация игрушечная и довольно простая, потому что все находится в одной единице трансляции. Пример такой, потому что внутри одной единицы компиляции порядок инициализации детерминирован, тут легче показать причину и следствие. Но когда мы выходим за ее пределы и пытаемся создать объект InitializationTest в другой единице трансляции в глобальном скоупе, то поведение кода начинает зависеть от линкера. Порядок создания объектов между юнитами компиляции не определен и тут все будет, как решит компановщик. Можно конечно почитать документацию и действовать в соответствии с ней. Но этот код будет непереносим, а также не защитит вас от возможных изменений в поведении линкера.
Будьте аккуратны с инициализацией статических объектов и в принципе поменьше их используйте.
Be careful. Stay cool.
Как думаете, может ли быть такое, что статическое поле класса инициализируется после завершения вызова конструктора класса? То есть объект уже создался, а статическое поле его класса еще не инициализированно? Подумайте пару секунд над этим вопросом самостоятельно.
После вчерашнего поста вам уже немного легче должны были даться рассуждения. Загвоздка в том, что статическое объекты инициализируются в порядке определения и не важно, какого класса эти объекты.
Посмотрим на пример.
Есть у нас класс, который сохраняет все свои инстансы по ключу в статическую мапу и все созданные инстансы доступны только через эту мапу.
class InitializationTest {
public:
static std::map<std::string, std::unique_ptr<InitializationTest>> map;
static bool Create(std::string ID) {
map.insert({ID, std::move(std::unique_ptr<InitializationTest>{new InitializationTest})});
return true;
}
private:
Test() = default;
};
static bool creation_result = InitializationTest::Create("qwe");
// Somehow handle result and process object
std::map<std::string, std::unique_ptr<InitializationTest>> InitializationTest::map{};
int main() {}
Чтобы такое провернуть, создаем в классе статическую мапу, статический метод Create, который предоставляет доступ к созданию объектов и объявляем конструктор класса приватным, чтобы никто снаружи не смог втихаря создать объект. Таким образом, доступ к объектам есть только через метод Create и статическую мапу.
Не имея представления о нюансах инициализации, взбрело нам в голову создать и использовать объект перед определением мапы.
Запуская все это дело, получим сегфолт. И да, да, вы все правильно поняли. Все из-за порядка инициализации.
Так как линкеру пофиг на тип статических объектов, он спокойно может поставить инициализацию статического поля класса после завершения работы конструктора объекта в глобальном неймспейсе. Вот и получается конфуз: объект надо создавать, а используемое поле не инициализировано. От того и падаем.
Здесь ситуация игрушечная и довольно простая, потому что все находится в одной единице трансляции. Пример такой, потому что внутри одной единицы компиляции порядок инициализации детерминирован, тут легче показать причину и следствие. Но когда мы выходим за ее пределы и пытаемся создать объект InitializationTest в другой единице трансляции в глобальном скоупе, то поведение кода начинает зависеть от линкера. Порядок создания объектов между юнитами компиляции не определен и тут все будет, как решит компановщик. Можно конечно почитать документацию и действовать в соответствии с ней. Но этот код будет непереносим, а также не защитит вас от возможных изменений в поведении линкера.
Будьте аккуратны с инициализацией статических объектов и в принципе поменьше их используйте.
Be careful. Stay cool.