Экземпляр Singleton, объявленный как статическая переменная метода GetInstance

Я видел реализации шаблонов Singleton, где переменная экземпляра была объявлена ​​как статическая переменная в методе GetInstance. Вот так:

SomeBaseClass &SomeClass::GetInstance()
{
   static SomeClass instance;
   return instance;
}

Я вижу следующие положительные стороны этого подхода:

  • Код проще, потому что он компилятор, который отвечает за создание этого объекта только при первом вызове GetInstance.
  • Код более безопасный, поскольку нет другого способа получить ссылку на экземпляр, но с помощью метода GetInstance, и нет другого способа изменить экземпляр, но внутри метода GetInstance.

Каковы отрицательные стороны этого подхода (за исключением того, что это не очень OOP-ish)? Является ли это потокобезопасным?

Ответы

Ответ 1

В С++ 11 он безопасен по потоку:

§6.7 [stmt.dcl] p4 Если элемент управления входит в объявление одновременно, когда переменная инициализируется, параллельное выполнение должно ждать завершения инициализации.

В С++ 03:

  • В g++ это поточно-безопасный.
    Но это связано с тем, что g++ явно добавляет код, чтобы гарантировать его.

Одна из проблем заключается в том, что если у вас есть два синглтона, и они пытаются использовать друг друга во время строительства и разрушения.

Прочтите это: Поиск статических задач инициализации С++

Вариант этой проблемы заключается в том, что доступ к singleton из деструктора глобальной переменной. В этой ситуации синглтон определенно был уничтожен, но метод get все равно вернет ссылку на уничтоженный объект.

Есть способы обойти это, но они беспорядочны и не стоят того. Просто не обращайтесь к singleton из деструктора глобальной переменной.

Более безопасное определение, но уродливое:
Я уверен, вы можете добавить некоторые соответствующие макросы, чтобы убрать это.

SomeBaseClass &SomeClass::GetInstance()
{
#ifdef _WIN32 
Start Critical Section Here
#elif  defined(__GNUC__) && (__GNUC__ > 3)
// You are OK
#else
#error Add Critical Section for your platform
#endif

    static SomeClass instance;

#ifdef _WIN32
END Critical Section Here
#endif 

    return instance;
}

Ответ 2

Он не является потокобезопасным, как показано. Язык С++ не работает в потоках, поэтому у вас нет неотъемлемых гарантий от языка. Вам придется использовать примитивы синхронизации платформы, например. Win32:: EnterCriticalSection() для защиты доступа.

Ваш конкретный подход будет проблематичным. b/c компилятор вставляет некоторый (небелковый безопасный) код для инициализации статического instance при первом вызове, скорее всего, это произойдет до того, как тело функции начнет выполнение (и, следовательно, до любая синхронизация может быть вызвана.)

Использование указателя глобального/статического элемента к SomeClass, а затем инициализация внутри синхронизированного блока окажется менее проблематичной для реализации.

#include <boost/shared_ptr.hpp>

namespace
{
  //Could be implemented as private member of SomeClass instead..
  boost::shared_ptr<SomeClass> g_instance;
}

SomeBaseClass &SomeClass::GetInstance()
{
   //Synchronize me e.g. ::EnterCriticalSection()
   if(g_instance == NULL)
     g_instance = boost::shared_ptr<SomeClass>(new SomeClass());
   //Unsynchronize me e.g. :::LeaveCriticalSection();
   return *g_instance;
}

Я не скомпилировал это так для иллюстративных целей. Он также полагается на библиотеку boost, чтобы получить то же самое время жизни (или около того) в качестве вашего первоначального примера. Вы также можете использовать std:: tr1 (С++ 0x).

Ответ 3

Согласно спецификациям, это также должно работать в VС++. Кто-нибудь знает, если это произойдет?

Просто добавьте ключевое слово volatile. Затем визуальный компилятор С++ должен сгенерировать мьютексы, если документ в msdn верен.

SomeBaseClass &SomeClass::GetInstance()
{
   static volatile SomeClass instance;
   return instance;
}

Ответ 4

Он разделяет все общие ошибки реализации Singleton, а именно:

  • Он не тестируется
  • Он не является потокобезопасным (это достаточно тривиально, чтобы увидеть, представляете ли вы два потока, входящие в функцию в то же время).
  • Это утечка памяти.

Я рекомендую никогда не использовать Singleton в любом производственном коде.