Отказ от ответственности: Я пришел из Java, и поэтому не имею понятия о том, как работает множество внутренних компонентов C ++ (и связанных с ними библиотек).
Я прочитал достаточно, чтобы знать, что двойная проверка блокировки — это зло, и для правильной и безопасной реализации одноэлементного шаблона требуются соответствующие инструменты.
Я полагаю, что следующий код может быть небезопасным, с учетом переупорядочения компилятора и назначения неинициализированных объектов, но я не уверен, что мне не хватает чего-то, чего я не понимаю в языке.
typedef boost::shared_ptr<A> APtr;
APtr g_a;
boost::mutex g_a_mutex;
const APtr& A::instance()
{
if (!g_a)
{
boost::mutex::scoped_lock lock(g_a_mutex);
if (!g_a)
{
g_a = boost::make_shared<A>();
}
}
return g_a;
}
Я считаю, что фактический код скомпилирован под C ++ 03.
Эта реализация небезопасна? Если так, то как?
Все это абсолютно необходимо в современном C ++. Синглтон код должен быть таким же простым для всех, кроме динозавров:
A& A::instance() {
static A a;
return a;
}
100% потокобезопасность в C ++ 11 и выше.
Тем не менее, я должен сделать следующее обязательное заявление: синглтоны — злой антипаттерн, и их следует запретить навсегда.
Да, предоставленный код небезопасен. Поскольку чтение выполняется вне мьютекса, вполне возможно, что изменения в g_a
сам было бы видно, но не то, что модификации объекта g_a
указывает на. В результате поток обойдет блокировку мьютекса и вернет указатель на не построенный объект.
Да, дважды проверенный шаблон блокировки небезопасен в архаичных языках. Я не могу сделать ничего лучше, чем объяснение в Что не так с этим исправлением для двойной проверки блокировки?:
Очевидно, проблема заключается в назначении экземпляра строки — компилятор может выделить объект и затем назначить ему указатель, ИЛИ установить указатель на то место, где он будет размещен, а затем выделить его.
Если вы использовали C ++ 11 std::shared_ptr
Вы могли бы использовать atomic_load
а также atomic_store
на объекте общего указателя, который гарантированно правильно скомпонован друг с другом и с мьютексами; но если вы используете C ++ 11, вы можете просто использовать динамически инициализированный static
переменная, которая гарантированно является поточно-ориентированной.