¿Cómo es que la implementación de Meyers de un Singleton es en realidad un Singleton?
He estado leyendo mucho sobre Singletons, cuándo deben usarse y cuándo no, y cómo implementarlos de manera segura. Estoy escribiendo en C ++ 11 y me encontré con la implementación inicializada diferida de un singleton de Meyer, como se ve en esta pregunta.
Esta implementación es:
static Singleton& instance()
{
static Singleton s;
return s;
}
Entiendo que esto es seguro para subprocesos frente a otras preguntas aquí en SO, pero lo que no entiendo es cómo se trata en realidad de un patrón singleton. He implementado singletons en otros idiomas y estos siempre terminan algo así como este ejemplo de Wikipedia :
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;
}
}
Cuando miro este segundo ejemplo, es muy intuitivo cómo se trata de un singleton, ya que la clase tiene una referencia a una instancia de sí misma y solo devuelve esa instancia. Sin embargo, en el primer ejemplo, no entiendo cómo esto evita que existan dos instancias del objeto. Entonces mis preguntas son:
- ¿Cómo aplica la primera implementación un patrón singleton? Supongo que tiene que ver con la palabra clave estática, pero espero que alguien pueda explicarme en profundidad lo que sucede bajo el capó.
- Entre estos dos estilos de implementación, ¿es preferible uno al otro? ¿Cuáles son los pros y los contras?
Gracias por cualquier ayuda,
Este es un singleton porque static
la duración del almacenamiento de una función local significa que solo existe una instancia de ese local en el programa.
En el fondo, esto puede considerarse aproximadamente equivalente al siguiente C++ 98 (e incluso podría ser implementado vagamente así por un compilador):
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();
}
Las brocas de seguridad para hilos hacen que todo sea un poco más complicado, pero es esencialmente lo mismo.
Si observamos una implementación real para C++ 11, hay una variable de protección para cada estática (como el booleano anterior), que también se usa para barreras y subprocesos. Mire la salida AMD64 de Clang para ver:
Singleton& instance() {
static Singleton instance;
return instance;
}
El ensamblaje AMD64 para instance
Clang 3.0 de Ubuntu en AMD64 en -O1 (cortesía de http://gcc.godbolt.org/ es:
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
Puede ver que hace referencia a una guardia global para ver si se requiere inicialización, usa __cxa_guard_acquire
, prueba la inicialización nuevamente, etc. Exactamente en casi todos los sentidos como la versión que publicó en Wikipedia, excepto usando el ensamblaje AMD64 y los símbolos/diseño especificados en Itanium ABI .
Tenga en cuenta que si ejecuta esa prueba, debe proporcionar Singleton
un constructor no trivial para que no sea un POD; de lo contrario, el optimizador se dará cuenta de que no tiene sentido hacer todo ese trabajo de protección/bloqueo.
// Singleton.hpp
class Singleton {
public:
static Singleton& Instance() {
static Singleton S;
return S;
}
private:
Singleton();
~Singleton();
};
Esta implementación se conoce como Singleton de Meyers. Scott Meyers dice:
"Este enfoque se basa en la garantía de C++ de que los objetos estáticos locales se inicializan cuando la definición del objeto se encuentra por primera vez durante una llamada a esa función". ... "Como beneficio adicional, si nunca llamas a una función que emula un objeto estático no local, nunca incurrirás en el costo de construir y destruir el objeto".
Cuando llama
Singleton& s=Singleton::Instance()
por primera vez, se crea el objeto y en cada siguiente llamada Singleton::Instance()
se devuelve el mismo objeto. Tema principal:
- sujeto al Fiasco de la Orden de Destrucción (el equivalente al Fiasco de la Orden de Inicialización )
Otra implementación se llama el confiable Singleton con fugas.
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;
Dos cuestiones:
- fugas, a menos que implemente una versión y se asegure de llamarla (una vez)
- no es seguro para subprocesos