Вероятно, вам следует прочитать книгу Александреску.
Что касается локальной статики, я некоторое время не использовал Visual Studio, но при компиляции с Visual Studio 2003 для каждой DLL была выделена одна локальная статика... поговорим о кошмаре отладки, я его немного запомню: /
1.Время жизни синглтона
Основная проблема синглетонов - управление временем жизни.
Если вы когда-либо пытаетесь использовать объект, вам нужно быть живым и здоровым.Таким образом, проблема возникает как из инициализации, так и из-за разрушения, что является распространенной проблемой в C ++ для глобальных переменных.
Инициализацию обычно проще всего исправить.Как предполагают оба метода, его достаточно просто инициализировать при первом использовании.
Разрушение немного сложнее.глобальные переменные уничтожаются в обратном порядке, в котором они были созданы.Так что в локальном статическом случае вы фактически не управляете вещами ....
2.Локальная статика
struct A
{
A() { B::Instance(); C::Instance().call(); }
};
struct B
{
~B() { C::Instance().call(); }
static B& Instance() { static B MI; return MI; }
};
struct C
{
static C& Instance() { static C MI; return MI; }
void call() {}
};
A globalA;
В чем здесь проблема?Давайте проверим порядок, в котором вызываются конструкторы и деструкторы.
Сначала выполняется этап построения:
A globalA;
, A::A()
называется A::A()
звонки B::B()
A::A()
звонки C::C()
Работает нормально, потому что мы инициализируем B
и C
экземпляры при первом доступе.
Во-вторых, фаза разрушения:
C::~C()
вызвана, поскольку она была построена последним из 3 B::~B()
и называется ... ups, он пытается получить доступ к экземпляру C
!
Таким образом, мы имеем неопределенное поведение при разрушении, гул ...
3.Новая стратегия
Идея здесь проста.глобальные встроенные модули инициализируются перед другими глобальными переменными, поэтому ваш указатель будет установлен на 0
до вызова любого написанного вами кода, это гарантирует, что тест:
S& S::Instance() { if (MInstance == 0) MInstance = new S(); return *MInstance; }
будетна самом деле проверьте, является ли экземпляр правильным.
Однако, как уже было сказано, здесь есть утечка памяти и худший деструктор, который никогда не вызывается.Решение существует и стандартизировано.Это вызов функции atexit
.
Функция atexit
позволяет указать действие, которое будет выполнено во время завершения работы программы.С этим мы можем написать синглтон хорошо:
// in s.hpp
class S
{
public:
static S& Instance(); // already defined
private:
static void CleanUp();
S(); // later, because that's where the work takes place
~S() { /* anything ? */ }
// not copyable
S(S const&);
S& operator=(S const&);
static S* MInstance;
};
// in s.cpp
S* S::MInstance = 0;
S::S() { atexit(&CleanUp); }
S::CleanUp() { delete MInstance; MInstance = 0; } // Note the = 0 bit!!!
Во-первых, давайте узнаем больше о atexit
.Сигнатура int atexit(void (*function)(void));
, т.е. она принимает указатель на функцию, которая ничего не принимает в качестве аргумента и ничего не возвращает.
Во-вторых, как это работает?Ну, точно так же, как и в предыдущем случае использования: при инициализации он создает стек указателей для вызова функции и при уничтожении очищает стек по одному элементу за раз.Таким образом, по сути, функции вызываются по принципу «первым пришел - первым обслужен».
Что здесь произойдет?
Конструкция при первом доступе (инициализация в порядке), Я регистрирую метод CleanUp
для времени выхода
Время выхода: вызывается метод CleanUp
.Он уничтожает объект (таким образом мы можем эффективно выполнять работу в деструкторе) и сбрасывает указатель на 0
, чтобы сигнализировать о нем.
Что произойдет, если (как в примере с * 1095)*, B
и C
) Я вызываю экземпляр уже уничтоженного объекта?Что ж, в этом случае, так как я установил указатель на 0
, я восстановлю временный синглтон, и цикл начнется заново.Хотя он долго не проживет, так как я отправляю свой стек.
Александреску назвал его Phoenix Singleton
, поскольку он воскресает из пепла, если он нужен после его уничтожения.
Еще одна альтернативадолжен иметь статический флаг и установить его на destroyed
во время очистки, и дать пользователю знать, что он не получил экземпляр синглтона, например, возвращая нулевой указатель.Единственная проблема, с которой я возвращаю указатель (или ссылку), состоит в том, что вам лучше надеяться, что никто не настолько глуп, чтобы вызывать delete
: /
4. Моноидный паттерн
Поскольку мы говорим о Singleton
, я думаю, пришло время представить шаблон Monoid
. По сути, его можно рассматривать как вырожденный случай шаблона Flyweight
или использование Proxy
over Singleton
.
Шаблон Monoid
прост: все экземпляры класса имеют общее состояние.
Я воспользуюсь возможностью раскрыть реализацию не-Phoenix:)
class Monoid
{
public:
void foo() { if (State* i = Instance()) i->foo(); }
void bar() { if (State* i = Instance()) i->bar(); }
private:
struct State {};
static State* Instance();
static void CleanUp();
static bool MDestroyed;
static State* MInstance;
};
// .cpp
bool Monoid::MDestroyed = false;
State* Monoid::MInstance = 0;
State* Monoid::Instance()
{
if (!MDestroyed && !MInstance)
{
MInstance = new State();
atexit(&CleanUp);
}
return MInstance;
}
void Monoid::CleanUp()
{
delete MInstance;
MInstance = 0;
MDestroyed = true;
}
В чем выгода? Он скрывает тот факт, что государство является общим, он скрывает Singleton
.
- Если вам когда-либо понадобится иметь два разных состояния, возможно, вам удастся сделать это, не меняя каждую строку кода, которая его использовала (например, заменив
Singleton
вызовом Factory
)
- Nodoby собирается позвонить
delete
на экземпляр вашего синглтона, чтобы вы действительно управляли состоянием и предотвращали несчастные случаи ... вы все равно ничего не можете сделать против злонамеренных пользователей!
- Вы контролируете доступ к синглтону, поэтому в случае, если он вызывается после его уничтожения, вы можете правильно с ним обращаться (ничего не делать, регистрировать и т. Д.)
5. Последнее слово
Как бы полно это не выглядело, я хотел бы отметить, что я с радостью рассмотрел любые проблемы с многопоточностью ... прочитайте Modern C ++ Александреску, чтобы узнать больше!