C ++ статический порядок инициализации
-
05-07-2019 - |
Вопрос
Когда я использую статические переменные в C ++, мне часто хочется инициализировать одну переменную, передавая другую ее конструктору. Другими словами, я хочу создать статические экземпляры, которые зависят друг от друга. Р>
В одном файле .cpp или .h это не проблема: экземпляры будут создаваться в порядке их объявления. Однако, когда вы хотите инициализировать статический экземпляр с экземпляром в другом модуле компиляции, порядок, кажется, невозможно указать. В результате, в зависимости от погоды, может случиться, что экземпляр, который зависит от другого, будет создан, и только после этого будет создан другой экземпляр. В результате первый экземпляр инициализируется неправильно.
Кто-нибудь знает, как обеспечить создание статических объектов в правильном порядке? Я долго искал решение, пробовал все из них (включая решение со счетчиком Шварца), но начинаю сомневаться, что есть одно, которое действительно работает.
Одной из возможностей является уловка со статическим членом функции:
Type& globalObject()
{
static Type theOneAndOnlyInstance;
return theOneAndOnlyInstance;
}
Действительно, это работает. К сожалению, вы должны написать globalObject (). MemberFunction () вместо globalObject.MemberFunction (), что приведет к некоторому запутанному и не элегантному клиентскому коду.
Обновление: Спасибо за вашу реакцию. К сожалению, похоже, что я действительно ответил на свой вопрос. Я думаю, мне придется научиться жить с этим ...
Решение
Вы ответили на свой вопрос. Порядок статической инициализации не определен, и наиболее элегантный способ его обойти (хотя статическая инициализация все еще выполняется, т.е. не выполняется его рефакторинг полностью) - это обернуть инициализацию в функцию.
Прочитайте раздел часто задаваемых вопросов по C ++, начиная с https://isocpp.org/wiki / Вопросы / ctors # статических INIT порядка
Другие советы
Возможно, вам следует пересмотреть, нужно ли вам так много глобальных статических переменных. Хотя иногда они могут быть полезны, зачастую гораздо проще их реорганизовать в меньшую локальную область, особенно если вы обнаружите, что некоторые статические переменные зависят от других.
Но вы правы, нет способа обеспечить определенный порядок инициализации, и поэтому, если ваше сердце настроено на это, сохранить инициализацию в функции, как вы упомянули, возможно, самый простой способ.
Действительно, это работает. К сожалению, вы должны написать globalObject (). MemberFunction () вместо globalObject.MemberFunction (), что приведет к некоторому запутанному и не элегантному клиентскому коду.
Но самое главное, что он работает, и что он защищен от сбоев, т.е. нелегко обойти правильное использование.
Корректность программы должна быть вашим первым приоритетом. Также, ИМХО, () выше чисто стилистический - т.е. совершенно неважно.
В зависимости от вашей платформы, будьте осторожны с чрезмерной динамической инициализацией. Для динамических инициализаторов может быть относительно небольшое количество очистки (см. здесь а>). Вы можете решить эту проблему, используя контейнер глобальных объектов, который содержит элементы различных глобальных объектов. Поэтому у вас есть:
Globals & getGlobals ()
{
static Globals cache;
return cache;
}
Существует только один вызов ~ Globals () для очистки всех глобальных объектов в вашей программе. Чтобы получить доступ к глобальному, у вас все еще есть что-то вроде:
getGlobals().configuration.memberFunction ();
Если вы действительно хотите, вы можете обернуть это в макрос, чтобы сэкономить немного текста при помощи макроса:
#define GLOBAL(X) getGlobals().#X
GLOBAL(object).memberFunction ();
Хотя это всего лишь синтаксический сахар в вашем первоначальном решении.
Большинство компиляторов (компоновщиков) действительно поддерживают (непереносимый) способ указания порядка. Например, в Visual Studio вы можете использовать init_seg Прагма организовать инициализацию в несколько разных групп. У AFAIK нет способа гарантировать порядок в каждой группе. Так как это непереносимо, вы можете подумать, можете ли вы исправить свой дизайн так, чтобы он не требовался, но опция есть.
Несмотря на возраст этой темы, я хотел бы предложить решение, которое я нашел. Как многие указывали ранее, 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;
}