Para responder a su pregunta sobre por qué no es seguro para subprocesos, no es porque la primera llamada a instance()
debe llamar al constructor Singleton s
. Para ser seguro para subprocesos, esto debería ocurrir en una sección crítica, pero no hay ningún requisito en el estándar de que se tome una sección crítica (el estándar hasta la fecha es completamente silencioso en los subprocesos). Los compiladores a menudo implementan esto usando una simple verificación e incremento de un booleano estático, pero no en una sección crítica. Algo así como el siguiente pseudocódigo:
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<Singleton*>( &s)));
}
Así que aquí hay un Singleton seguro para subprocesos simple (para Windows). Utiliza un contenedor de clase simple para el objeto CRITICAL_SECTION de Windows para que podamos hacer que el compilador inicialice automáticamente CRITICAL_SECTION
antes de que main()
se llame. Idealmente, se usaría una verdadera clase de sección crítica RAII que pueda manejar las excepciones que podrían ocurrir cuando se realiza la sección crítica, pero eso está más allá del alcance de esta respuesta.
La operación fundamental es que cuando Singleton
se solicita una instancia de , se toma un bloqueo, se crea Singleton si es necesario, luego se libera el bloqueo y se devuelve la referencia Singleton.
#include <windows.h>
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;
}
Hombre, eso es un montón de basura para "hacer un mundo mejor".
Los principales inconvenientes de esta implementación (si no dejé pasar algunos errores) es:
- si se
new Singleton()
lanza, el bloqueo no se liberará. Esto se puede solucionar mediante el uso de un verdadero objeto de bloqueo RAII en lugar del simple que tengo aquí. Esto también puede ayudar a hacer que las cosas sean portátiles si usa algo como Boost para proporcionar un contenedor independiente de la plataforma para la cerradura.
- esto garantiza la seguridad de los subprocesos cuando se solicita la instancia Singleton después de que
main()
se llame; si la llama antes (como en la inicialización de un objeto estático), las cosas podrían no funcionar porque CRITICAL_SECTION
podrían no inicializarse.
- Se debe tomar un candado cada vez que se solicita una instancia. Como dije, esta es una implementación simple y segura para subprocesos. Si necesita una mejor (o quiere saber por qué fallan cosas como la técnica de bloqueo de doble verificación), consulte los documentos vinculados en la respuesta de Groo .