Обычное решение этой проблемы - использовать статический объект, который можно инициализировать нулем или инициализировать константой в сочетании с атомарными операциями, чтобы «загрузить» себя в положение, в котором вы можете безопасно вызывать более сложную инициализацию.
Нулевая и постоянная инициализация гарантированно произойдут до непостоянной инициализации, и, поскольку она фактически происходит одновременно, она не зависит от порядка инициализации.
Использовать Ленивый Инициализированный Объект
В очень простом примере будет использоваться инициализированный нулем указатель на глобальный статический экземпляр, который указывает, была ли статическая инициализирована, например:
class A
{
private:
volatile static A* Instance; // zero-initialized to NULL
public:
static A& GetInstance() {
A* inst = Instance;
if (!inst) {
A* inst = new Instance(...);
A* cur = InterlockedCompareExchange(&Instance, newInst, 0);
if (cur) {
delete inst;
return *cur;
}
}
return *inst;
}
};
Недостатком вышеупомянутого подхода является то, что два (или более) одного объекта A
могут быть созданы, если два (или более) потока оба первоначально видят A::Instance
как ноль. Код правильно выбирает только один объект A
, который будет истинным статическим глобальным значением, возвращаемым всем вызывающим, а остальные просто удаляются без вывода сообщений, но это может быть проблемой, когда даже невозможно создать более одного объекта Instance
в процессе (например, потому что он поддерживается каким-то фундаментально одноэлементным ресурсом, возможно, дескриптором некоторого аппаратного ресурса). Если будет создано более одного Instance
, будет также потрачено немного времени, что может иметь значение, если процесс создания будет дорогим.
Этот шаблон иногда называют racy-single-check .
Используйте инициализированный ленивый мьютекс
Лучшее решение, позволяющее избежать вышеуказанных ошибок, - использовать мьютекс для защиты создания синглтона. Конечно, теперь инициализация мьютекса имеет ту же проблему упорядочения, но мы можем использовать вышеописанный трюк, чтобы исправить это (и мы знаем, что можно создать более одного объекта мьютекса).
class MutexHolder
{
private:
volatile static CRITICAL_SECTION* cs; // zero-initialized to NULL
public:
static CRITICAL_SECTION* get() {
A* inst = cs;
if (!inst) {
CRITICAL_SECTION* inst = new CRITICAL_SECTION();
InitializeCriticalSection(inst);
CRITICAL_SECTION* cur = InterlockedCompareExchange(&cs, newInst, 0);
if (cur) {
DeleteCriticalSection(inst);
delete inst;
return *cur;
}
}
return *inst;
}
};
class A
{
private:
static MutexHolder mutex;
static A* Instance; // zero-initialized to NULL
public:
static A& GetInstance() {
A* inst;
CRITICAL_SECTION *cs = mutex.get();
EnterCriticalSection(cs);
if (!(inst = Instance)) {
inst = Instance = new A(...);
}
EnterCriticalSection(cs);
return inst;
}
};
Здесь MutexHolder
- это многоразовая оболочка для объекта Windows CRITICAL_SECTION
, которая выполняет отложенную и поточно-ориентированную инициализацию внутри метода get()
и может быть инициализирована нулями. Этот MutexHolder
затем используется как классический мьютекс для защиты создания статического A
объекта внутри A::GetInstance
.
Вы можете сделать GetInstance
быстрее за счет некоторой сложности с помощью двойной проверки блокировки : вместо того, чтобы безоговорочно получить CRITICAL_SECTION
, сначала проверьте, установлен ли Instance
(например, первый пример), а затем вернуть его напрямую, если он есть.
InitOnceExecuteOnce
Наконец, если вы ориентируетесь на Windows Vista или более позднюю версию, Microsoft добавила готовый инструмент, который обрабатывает это напрямую: InitOnceExecuteOnce . Вы можете найти сработавший пример здесь . Это примерно аналогично POSIX pthead_once
и работает, потому что инициализация выполняется с использованием константы INIT_ONCE_STATIC_INIT
.
В вашем случае это будет выглядеть примерно так:
INIT_ONCE g_InitOnce = INIT_ONCE_STATIC_INIT;
A* g_AInstance = 0;
BOOL CALLBACK MakeA(
PINIT_ONCE InitOnce,
PVOID Parameter,
PVOID *lpContext)
{
g_AInstance = new A(...);
return TRUE;
}
class A
{
private:
public:
static A& GetInstance() {
// Execute the initialization callback function
bStatus = InitOnceExecuteOnce(&g_InitOnce,
MakeA,
NULL,
NULL);
assert(bStatus);
return *g_AInstance;
}
};
Раймонд Чен написал в блоге запись об этой функции , которая также способствует хорошему чтению.