C++ статический порядок инициализации
Когда я использую статические переменные в C++, мне часто приходится инициализировать одну переменную, передавая другую ее конструктору. Другими словами, я хочу создать статические экземпляры, которые зависят друг от друга.
В пределах одного файла.cpp или.h это не проблема: экземпляры будут создаваться в порядке их объявления. Однако, когда вы хотите инициализировать статический экземпляр с экземпляром в другом модуле компиляции, порядок, кажется, невозможно определить. В результате, в зависимости от погоды, может случиться так, что экземпляр, который зависит от другого, будет создан, и только после этого будет создан другой экземпляр. В результате первый экземпляр инициализируется неправильно.
Кто-нибудь знает, как обеспечить создание статических объектов в правильном порядке? Я долго искал решение, пробовал все из них (в том числе решение счетчика Шварца), но начинаю сомневаться, что есть одно, которое действительно работает.
Одна из возможностей - фокус со статическим членом функции:
Type& globalObject()
{
static Type theOneAndOnlyInstance;
return theOneAndOnlyInstance;
}
Действительно, это работает. К сожалению, вы должны написать globalObject().MemberFunction(), а не globalObject.MemberFunction(), что приведет к некоторому запутанному и не элегантному клиентскому коду.
Обновление: спасибо за ваши реакции. К сожалению, мне действительно кажется, что я ответил на свой вопрос. Я думаю, мне придется научиться жить с этим...
6 ответов
Вы ответили на свой вопрос. Порядок статической инициализации не определен, и самый элегантный способ его обойти (хотя статическая инициализация все еще выполняется, т.е. не выполняется рефакторинг), это заключить в инициализацию функцию.
Прочитайте раздел часто задаваемых вопросов по C++, начиная с https://isocpp.org/wiki/faq/ctors.
Возможно, вам следует пересмотреть, нужно ли вам так много глобальных статических переменных. Хотя иногда они могут быть полезны, часто гораздо проще реорганизовать их в меньшую локальную область, особенно если вы обнаружите, что некоторые статические переменные зависят от других.
Но вы правы, нет способа обеспечить определенный порядок инициализации, и поэтому, если ваше сердце настроено на это, сохранить инициализацию в функции, как вы упомянули, возможно, самый простой способ.
Большинство компиляторов (компоновщиков) действительно поддерживают (непереносимый) способ указания порядка. Например, в Visual Studio вы можете использовать прагму init_seg, чтобы организовать инициализацию в несколько разных групп. У AFAIK нет способа гарантировать порядок в каждой группе. Так как это непереносимо, вы можете подумать, можете ли вы исправить свой дизайн так, чтобы он не требовался, но опция есть.
Действительно, это работает. К сожалению, вы должны написать globalObject().MemberFunction(), а не globalObject.MemberFunction(), что приведет к некоторому запутанному и не элегантному клиентскому коду.
Но самое главное, что это работает, и что это доказательство отказа, т.е. не легко обойти правильное использование.
Корректность программы должна быть вашим первым приоритетом. Также, ИМХО, () выше является чисто стилистическим - т.е. совершенно неважно.
В зависимости от вашей платформы, будьте осторожны с слишком большой динамической инициализацией. Для динамических инициализаторов может быть относительно небольшое количество очистки (см. Здесь). Вы можете решить эту проблему, используя контейнер глобальных объектов, который содержит элементы различных глобальных объектов. Поэтому у вас есть:
Globals & getGlobals ()
{
static Globals cache;
return cache;
}
Существует только один вызов ~Globals() для очистки всех глобальных объектов в вашей программе. Чтобы получить доступ к глобальному, у вас все еще есть что-то вроде:
getGlobals().configuration.memberFunction ();
Если вы действительно хотите, вы можете обернуть это в макрос, чтобы сэкономить немного текста, используя макрос:
#define GLOBAL(X) getGlobals().#X
GLOBAL(object).memberFunction ();
Хотя это всего лишь синтаксический сахар в вашем первоначальном решении.
Несмотря на возраст этой темы, я хотел бы предложить решение, которое я нашел. Как многие указывали ранее, C++ не предоставляет никакого механизма для статического упорядочения инициализации. Я предлагаю заключить в капсулу каждый статический член внутри статического метода класса, который, в свою очередь, инициализирует член и обеспечивает доступ объектно-ориентированным способом. Позвольте мне привести пример, предположив, что мы хотим определить класс с именем "Math", который, среди других членов, содержит "PI":
class Math {
public:
static const float Pi() {
static const float s_PI = 3.14f;
return s_PI;
}
}
s_PI будет инициализирован при первом вызове метода Pi() (в GCC). Имейте в виду: локальные объекты со статическим хранилищем имеют зависящий от реализации жизненный цикл, для дальнейшей детальной проверки 6.7.4 в 2.
Обертывание статики в методе исправит проблему с порядком, но она не является поточно-ориентированной, как отмечали другие, но вы также можете сделать это, чтобы сделать ее поточной, если это вызывает озабоченность.
// File scope static pointer is thread safe and is initialized first.
static Type * theOneAndOnlyInstance = 0;
Type& globalObject()
{
if(theOneAndOnlyInstance == 0)
{
// Put mutex lock here for thread safety
theOneAndOnlyInstance = new Type();
}
return *theOneAndOnlyInstance;
}