Существует несколько возможностей для глобального состояния (изменяемого или нет).
Если вы опасаетесь, что у вас возникнет проблема с инициализацией, вам следует использовать подход local static
для создания своего экземпляра.
Обратите внимание, что представленный вами неуклюжий синглтон-дизайн не является обязательным:
class Singleton
{
public:
static void DoSomething(int i)
{
Singleton& s = Instance();
// do something with i
}
private:
Singleton() {}
~Singleton() {}
static Singleton& Instance()
{
static Singleton S; // no dynamic allocation, it's unnecessary
return S;
}
};
// Invocation
Singleton::DoSomething(i);
Другой дизайн несколько похож, хотя я предпочитаю его, потому что он значительно упрощает переход к неглобальному дизайну..
class Monoid
{
public:
Monoid()
{
static State S;
state = &s;
}
void doSomething(int i)
{
state->count += i;
}
private:
struct State
{
int count;
};
State* state;
};
// Use
Monoid m;
m.doSomething(1);
Чистым преимуществом здесь является то, что "глобальность" состояния скрыта, это детали реализации, о которых клиентам не нужно беспокоиться.Очень полезно для кешей.
Позвольте нам задать вопрос о дизайне:
- действительно ли вам нужно обеспечить особенность?
- действительно ли вам нужнообъект будет построен до
main
запуска?
Сингулярность обычно переоценивается.C ++ 0x здесь поможет, но даже тогда техническая поддержка сингулярности вместо того, чтобы полагаться на поведение программистов, может быть очень раздражающей ... например, при написании тестов: действительно ли вы хотите выгружать / перезагружать вашу программу между каждым модульным тестомпросто изменить конфигурацию между каждым?Тьфу.Гораздо проще создать его один раз и поверить в своих коллег-программистов ... или в функциональные тесты;)
Второй вопрос скорее технический, чем функциональный.Если вам нужна конфигурация перед точкой входа вашей программы, вы можете просто прочитать ее при запуске.
Это может показаться наивным, но на самом деле есть одна проблема с вычислениями во время загрузки библиотеки: как сделатьвы обрабатываете ошибки?Если бросить, библиотека не загружается.Если вы не бросаете и продолжаете, вы находитесь в недопустимом состоянии.Не так смешно, правда?Ситуация становится намного проще, когда настоящая работа началась, потому что вы можете использовать обычную логику потока управления.
И если вы подумаете о проверке, действительно ли состояние действительно или нет ... почему бы просто не собрать все вточка, в которой вы будете тестировать?
Наконец, самая большая проблема с global
- это вводимые скрытые зависимости.Гораздо лучше, когда зависимости неявно связаны с потоком выполнения или влиянием рефакторинга.
EDIT :
Относительно проблем порядка инициализации:объекты в пределах одной единицы перевода гарантированно инициализируются в том порядке, в котором они определены.
Таким образом, следующий код действителен в соответствии со стандартом:
static int foo() { return std::numeric_limits<int>::max() / 2; }
static int bar(int c) { return c*2; }
static int const x = foo();
static int const y = bar(x);
Порядок инициализации толькопроблема при ссылке на константы / переменные, определенные в другом модуле перевода.Таким образом, static
объекты могут быть естественно выражены без проблем, если они относятся только к static
объектам в пределах одной и той же единицы перевода.
Относительно проблемы с пространством: правило as-if
может творить чудеса здесь,Неформально правило as-if
означает, что вы задаете поведение и оставляете его на усмотрение компилятора / компоновщика / среды выполнения, не заботясь о том, как оно предоставляется.Это то, что действительно позволяет оптимизировать.
Поэтому, если цепочка компиляторов может сделать вывод, что адрес константы никогда не берется, она может полностью исключить константу.Если он может заключить, что несколько констант всегда будут равны, и еще раз, что их адрес никогда не проверяется, он может объединить их.