Является ли реализация Meyers streamом шаблонов Singleton безопасным?

Является ли следующая реализация, использующая ленивую инициализацию streamом Singleton (Meyers ‘Singleton) безопасной?

 static Singleton& instance() { static Singleton s; return s; } 

Если нет, то почему и как сделать его streamобезопасным?

В C ++ 11 он является streamобезопасным. Согласно стандарту , §6.7 [stmt.dcl] p4 :

Если элемент управления входит в объявление одновременно, а переменная инициализируется, одновременное выполнение должно ждать завершения инициализации.

Поддержка GCC и VS для этой функции ( Dynamic Initialization and Destruction with Concurrency , также известная как Magic Statics на MSDN ) выглядит следующим образом:

  • Visual Studio: поддерживается с Visual Studio 2015
  • GCC: поддерживается с GCC 4.3

Спасибо @Mankarse и @olen_gam за их комментарии.


В C ++ 03 этот код не был streamобезопасным. Существует статья Мейерса под названием «C ++ и« Опасности блокировки с двойной проверкой », в которой обсуждаются поточно-безопасные реализации шаблона, и вывод более или менее заключается в том, что (в C ++ 03) полная блокировка по методу создания экземпляра в основном, является самым простым способом обеспечения правильного параллелизма на всех платформах, в то время как большинство форм двойных проверок вариантов шаблонов могут пострадать от условий гонки на определенных архитектурах , если только команды не чередуются с страtagsчески мерами памяти.

Чтобы ответить на ваш вопрос о том, почему он не является streamобезопасным, это происходит не потому, что первый вызов instance() должен вызвать конструктор для Singleton s . Чтобы быть streamобезопасными, это должно было произойти в критическом разделе, но в стандарте нет требования о том, чтобы критический раздел был принят (стандарт на сегодняшний день полностью отключен для streamов). Компиляторы часто реализуют это, используя простую проверку и приращение статического булева, но не в критическом разделе. Что-то вроде следующего псевдокода:

 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( &s))); } 

Итак, вот простой streamобезопасный Singleton (для Windows). Он использует простую оболочку classа для объекта Windows CRITICAL_SECTION, чтобы мы могли заставить компилятор автоматически инициализировать CRITICAL_SECTION до CRITICAL_SECTION main() . В идеале должен использоваться истинный class критического раздела RAII, который может иметь дело с исключениями, которые могут возникнуть при проведении критического раздела, но это выходит за frameworks этого ответа.

Основная операция заключается в том, что, когда запрашивается экземпляр Singleton , происходит блокировка, Singleton создается, если это необходимо, затем блокировка освобождается и возвращается ссылка Singleton.

 #include  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 для обеспечения независимой от платформы оболочки для блокировки.
  • это гарантирует безопасность streamа, когда экземпляр Singleton запрашивается после вызова main() – если вы вызываете его до этого (например, при инициализации статического объекта), все может не работать, потому что CRITICAL_SECTION может не быть инициализировано.
  • блокировка должна выполняться каждый раз, когда запрашивается экземпляр. Как я уже сказал, это простая поточная реализация. Если вам нужен лучший (или хотите знать, почему такие вещи, как метод блокировки двойной проверки, являются ошибочными), см. Документы, связанные с ответом Гроо .

Рассматривая следующий стандарт (раздел 6.7.4), он объясняет, насколько статическая локальная инициализация является streamобезопасной. Поэтому, когда эта часть стандарта будет широко внедрена, предпочтительной реализацией станет Meyer’s Singleton.

Я уже не согласен со многими ответами. Большинство компиляторов уже реализуют статическую инициализацию таким образом. Одним из примечательных исключений является Microsoft Visual Studio.

Правильный ответ зависит от вашего компилятора. Он может решить сделать это streamобезопасным; это не «naturallly» threadsafe.

Является ли следующая реализация […] streamовой безопасностью?

На большинстве платформ это не является streamобезопасным. (Приложите обычный отказ от ответственности, объяснив, что стандарт C ++ не знает о streamах, так что на законных основаниях он не говорит, является это или нет.)

Если нет, то почему […]?

Причина этого не в том, что ничто не препятствует одновременному выполнению конструктора s более одного streamа.

как сделать его streamобезопасным?

«C ++ и опасности двойного контроля» Скотта Мейерса и Андрея Александреску – довольно хороший трактат по теме streamобезопасных синглетонов.

Как сказал MSalters: это зависит от используемой вами реализации C ++. Проверьте документацию. Что касается другого вопроса: «Если нет, то почему?» – Стандарт C ++ ничего не говорит о streamах. Но предстоящая версия C ++ знает streamи, и она явно заявляет, что инициализация статических локальных сетей является streamобезопасной. Если два streamа вызывают такую ​​функцию, один stream будет выполнять инициализацию, а другой будет блокировать и ждать завершения.

Давайте будем гением компьютера.