Каким образом реализация Майерсом Синглтона на самом деле является синглтон
Я много читал о синглетонах, когда их следует и не следует использовать, и как их безопасно применять. Я пишу на С++ 11 и натолкнулся на ленивую инициализованную Meyer реализацию синглтона, как видно из этого вопроса.
Эта реализация:
static Singleton& instance()
{
static Singleton s;
return s;
}
Я понимаю, как это защищено от других вопросов здесь, на SO, но я не понимаю, как это на самом деле одноэлементный шаблон. Я реализовал синглтоны на других языках, и они всегда заканчиваются чем-то вроде этого примера из Википедии:
public class SingletonDemo {
private static volatile SingletonDemo instance = null;
private SingletonDemo() { }
public static SingletonDemo getInstance() {
if (instance == null) {
synchronized (SingletonDemo .class){
if (instance == null) {
instance = new SingletonDemo ();
}
}
}
return instance;
}
}
Когда я смотрю на этот второй пример, становится очень интуитивно понятно, как это синглтон, поскольку класс содержит ссылку на один экземпляр самого себя и только когда-либо возвращает этот экземпляр. Однако в первом примере я не понимаю, как это предотвращает существование двух экземпляров объекта. Итак, мои вопросы:
- Как в первой реализации применяется одноэлементный шаблон? Я предполагаю, что это связано с ключевым словом static, но я надеюсь, что кто-то сможет подробно объяснить мне, что происходит под капотом.
- Между этими двумя стилями реализации один предпочтительнее другого? Каковы плюсы и минусы?
Спасибо за любую помощь,
Ответы
Ответ 1
Это singleton, потому что static
время хранения для функции local означает, что в программе существует только один экземпляр этого локального объекта.
Под капотом это можно очень грубо считать эквивалентным следующему С++ 98 (и даже может быть реализовано смутно подобным образом компилятором):
static bool __guard = false;
static char __storage[sizeof(Singleton)]; // also align it
Singleton& Instance() {
if (!__guard ) {
__guard = true;
new (__storage) Singleton();
}
return *reinterpret_cast<Singleton*>(__storage);
}
// called automatically when the process exits
void __destruct() {
if (__guard)
reinterpret_cast<Singleton*>(__storage)->~Singleton();
}
Биты безопасности потока делают его немного сложнее, но по сути это одно и то же.
Глядя на фактическую реализацию для С++ 11, для каждой статической (например, булевой) существует защитная переменная, которая также используется для барьеров и потоков. Посмотрите на выход Clang AMD64 для:
Singleton& instance() {
static Singleton instance;
return instance;
}
Сборник AMD64 для instance
от Ubuntu Clang 3.0 на AMD64 на -O1 (с учетом http://gcc.godbolt.org/:
instance(): # @instance()
pushq %rbp
movq %rsp, %rbp
movb guard variable for instance()::instance(%rip), %al
testb %al, %al
jne .LBB0_3
movl guard variable for instance()::instance, %edi
callq __cxa_guard_acquire
testl %eax, %eax
je .LBB0_3
movl instance()::instance, %edi
callq Singleton::Singleton()
movl guard variable for instance()::instance, %edi
callq __cxa_guard_release
.LBB0_3:
movl instance()::instance, %eax
popq %rbp
ret
Вы можете видеть, что он ссылается на глобальную охрану, чтобы узнать, требуется ли инициализация, использует __cxa_guard_acquire
, снова проверяет инициализацию и так далее. Точно почти так, как версия, которую вы опубликовали из Википедии, за исключением использования сборки AMD64 и символов/макетов, указанных в Itanium ABI.
Обратите внимание: если вы запустите этот тест, вы должны дать Singleton
нетривиальный конструктор, чтобы он не был POD, иначе оптимизатор поймет, что нет смысла делать все, что защищает/блокирует работу.
Ответ 2
// Singleton.hpp
class Singleton {
public:
static Singleton& Instance() {
static Singleton S;
return S;
}
private:
Singleton();
~Singleton();
};
Эта реализация известна как Singleton Meyers. Скотт Мейерс говорит:
"Этот подход основан на гарантии С++, что локальные статические объекты инициализируются при первом обнаружении объекта во время вызова этой функции."..." В качестве бонуса, если вы никогда не звоните функция, эмулирующая нелокальный статический объект, вы никогда не берете на себя стоимость построения и разрушения объекта.
Когда вы звоните
Singleton& s=Singleton::Instance()
первый раз, когда объект создается, и каждый следующий вызов Singleton::Instance()
приводит к возврату того же объекта.
Основная проблема:
Другая реализация называется надежным пропуском Singleton.
class Singleton {
public:
static Singleton& Instance() {
if (I == nullptr) { I = new Singleton(); }
return *I;
}
private:
Singleton();
~Singleton();
static Singleton* I;
};
// Singleton.cpp
Singleton* Singleton::I = 0;
Два вопроса:
- утечки, если вы не реализуете Release и не вызываете его (один раз)
- небезопасный поток