TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.35K subscribers
Post #247 3.48K
​Инициализация статических полей класса. Ч2

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

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

Посмотрим на пример.

Есть у нас класс, который сохраняет все свои инстансы по ключу в статическую мапу и все созданные инстансы доступны только через эту мапу.

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.
  • 👍 20
  • 🔥 10
  • 🫡 2
  • ❤ 1
More from @grokaemcpp
  1. Oct 14, 2026​​Что внутри std::condition_variable? #опытным Более менее все знакомы с примитивом синхро…
  2. Oct 12, 2026​​WAT #новичкам Как вы думаете, результат выражения: bool math = -1 < 1U; будет правдой ил…
  3. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  4. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  5. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  6. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →