Чтобы ответить на ваш вопрос о том, почему это не потокобезопасно, это не потому, что первый вызов instance()
должен вызвать конструктор для Singleton s
. Чтобы быть потокобезопасным, это должно происходить в критической секции, но в стандарте нет требования, что критическая секция должна быть взята (стандарт на сегодняшний день совершенно ничего не говорит о потоках). Компиляторы часто реализуют это, используя простую проверку и приращение статического логического значения - но не в критической секции. Что-то вроде следующего псевдокода:
static Singleton& instance()
{
static bool initialized = false;
static char s[sizeof( Singleton)];
if (!initialized) {
initialized = true;
new( &s) Singleton(); // call placement new on s to construct it
}
return (*(reinterpret_cast<Singleton*>( &s)));
}
Итак, вот простой потокобезопасный синглтон (для Windows). Он использует простую оболочку класса для объекта Windows CRITICAL_SECTION, так что мы можем заставить компилятор автоматически инициализировать CRITICAL_SECTION
перед вызовом main()
. В идеале должен использоваться настоящий класс критической секции RAII, который может иметь дело с исключениями, которые могут возникнуть при удержании критической секции, но это выходит за рамки этого ответа.
Основная операция заключается в том, что когда запрашивается экземпляр Singleton
, берется блокировка, создается Singleton, если это необходимо, затем блокировка освобождается и возвращается ссылка Singleton.
#include <windows.h>
class CritSection : public CRITICAL_SECTION
{
public:
CritSection() {
InitializeCriticalSection( this);
}
~CritSection() {
DeleteCriticalSection( this);
}
private:
// disable copy and assignment of CritSection
CritSection( CritSection const&);
CritSection& operator=( CritSection const&);
};
class Singleton
{
public:
static Singleton& instance();
private:
// don't allow public construct/destruct
Singleton();
~Singleton();
// disable copy & assignment
Singleton( Singleton const&);
Singleton& operator=( Singleton const&);
static CritSection instance_lock;
};
CritSection Singleton::instance_lock; // definition for Singleton's lock
// it's initialized before main() is called
Singleton::Singleton()
{
}
Singleton& Singleton::instance()
{
// check to see if we need to create the Singleton
EnterCriticalSection( &instance_lock);
static Singleton s;
LeaveCriticalSection( &instance_lock);
return s;
}
Человек - это много дерьма, чтобы "улучшить глобальность".
Основные недостатки этой реализации (если я не позволил некоторым ошибкам проскользнуть):
- , если
new Singleton()
сработает, блокировка не будет снята. Это можно исправить с помощью настоящего объекта блокировки RAII вместо простого, который я здесь имею. Это также может помочь сделать вещи переносимыми, если вы используете что-то вроде Boost для предоставления независимой от платформы оболочки для блокировки.
- это гарантирует безопасность потока, когда экземпляр Singleton запрашивается после вызова
main()
- если вы вызываете его раньше (как при инициализации статического объекта), вещи могут не работать, потому что CRITICAL_SECTION
может не инициализироваться.
- блокировка должна выполняться каждый раз, когда запрашивается экземпляр. Как я уже сказал, это простая потокобезопасная реализация. Если вам нужен лучший (или вы хотите знать, почему такие вещи, как техника двойной проверки блокировки, неверны), см. документы, на которые есть ссылка в ответе Гроо .