Patrón de diseño C ++ Singleton

Recientemente me he topado con una realización / implementación del patrón de diseño de Singleton para C ++. Se ha visto así (lo he adoptado del ejemplo de la vida real):

// a lot of methods are omitted here class Singleton { public: static Singleton* getInstance( ); ~Singleton( ); private: Singleton( ); static Singleton* instance; }; 

De esta statement puedo deducir que el campo de instancia se inicia en el montón. Eso significa que hay una asignación de memoria. Lo que no está completamente claro para mí es cuándo exactamente se va a desasignar la memoria. ¿O hay un error y una fuga de memoria? Parece que hay un problema en la implementación.

Mi pregunta principal es, ¿cómo la implemento de la manera correcta?

En 2008 proporcioné una implementación C ++ 98 del patrón de diseño de Singleton que es evaluado de forma diferida, garantizada-destrucción, no técnicamente segura para subprocesos:
¿Alguien puede proporcionarme una muestra de Singleton en c ++?

Aquí hay una implementación actualizada de C ++ 11 del patrón de diseño de Singleton que se evalúa de forma diferida, se destruye correctamente y es seguro para la ejecución de subprocesos .

 class S { public: static S& getInstance() { static S instance; // Guaranteed to be destroyed. // Instantiated on first use. return instance; } private: S() {} // Constructor? (the {} brackets) are needed here. // C++ 03 // ======== // Don't forget to declare these two. You want to make sure they // are unacceptable otherwise you may accidentally get copies of // your singleton appearing. S(S const&); // Don't Implement void operator=(S const&); // Don't implement // C++ 11 // ======= // We can use the better technique of deleting the methods // we don't want. public: S(S const&) = delete; void operator=(S const&) = delete; // Note: Scott Meyers mentions in his Effective Modern // C++ book, that deleted functions should generally // be public as it results in better error messages // due to the compilers behavior to check accessibility // before deleted status }; 

Vea este artículo sobre cuándo usar un singleton: (no a menudo)
Singleton: ¿Cómo debería usarse?

Consulte este dos artículos sobre el orden de inicialización y cómo hacer frente a:
Orden de inicialización de variables estáticas
Encontrar problemas de orden de inicialización estática de C ++

Vea este artículo que describe las vidas:
¿Cuál es el tiempo de vida de una variable estática en una función de C ++?

Consulte este artículo que trata algunas implicaciones de subprocesos para singletons:
Instancia Singleton declarada como variable estática del método GetInstance, ¿es seguro para subprocesos?

Consulte este artículo que explica por qué el locking comprobado doble no funcionará en C ++:
¿Cuáles son todos los comportamientos indefinidos comunes que un progtwigdor de C ++ debe conocer?
Dr. Dobbs: C ++ y los peligros del locking doblemente controlado: parte I

Al ser un Singleton, por lo general no desea que se destruya.

Se desmantelará y se desasignará cuando finalice el progtwig, que es el comportamiento normal y deseado para un singleton. Si desea poder limpiarlo explícitamente, es bastante fácil agregar un método estático a la clase que le permite restaurarlo a un estado limpio, y hacer que se reasigne la próxima vez que se use, pero eso está fuera del scope de una singleton “clásico”.

Podría evitar la asignación de memoria. Hay muchas variantes, todas tienen problemas en el caso del entorno de subprocesamiento múltiple.

Prefiero este tipo de implementación (en realidad, no se dice correctamente que prefiero, porque evito los singletons tanto como sea posible):

 class Singleton { private: Singleton(); public: static Singleton& instance() { static Singleton INSTANCE; return INSTANCE; } }; 

No tiene asignación de memoria dinámica.

Otra alternativa no asignada: crea un singleton, digamos de clase C , según lo necesites:

 singleton() 

utilizando

 template  X& singleton() { static X x; return x; } 

Ni esta ni la respuesta de Cătălin son automáticamente seguras para subprocesos en C ++ actual, pero estarán en C ++ 0x.

La respuesta de @Loki Astari es excelente.

Sin embargo, hay momentos con múltiples objetos estáticos donde debe poder garantizar que el singleton no se destruirá hasta que todos sus objetos estáticos que usen el singleton ya no lo necesiten.

En este caso, std::shared_ptr se puede usar para mantener el singleton activo para todos los usuarios, incluso cuando se std::shared_ptr los destructores estáticos al final del progtwig:

 class Singleton { public: Singleton(Singleton const&) = delete; Singleton& operator=(Singleton const&) = delete; static std::shared_ptr instance() { static std::shared_ptr s{new Singleton}; return s; } private: Singleton() {} }; 

Si desea asignar el objeto en el montón, ¿por qué no utiliza un puntero único? La memoria también será desasignada ya que estamos usando un puntero único.

 class S { public: static S& getInstance() { if( m_s.get() == 0 ) { m_s.reset( new S() ); } return *m_s; } private: static std::unique_ptr m_s; S(); S(S const&); // Don't Implement void operator=(S const&); // Don't implement }; std::unique_ptr S::m_s(0); 

La solución en la respuesta aceptada tiene un inconveniente importante: se llama al destructor para el singleton después de que el control deja la función “principal”. Puede haber problemas realmente, cuando algunos objetos dependientes se asignan dentro de “principal”.

Conocí este problema al intentar introducir un Singleton en la aplicación Qt. Decidí que todos los cuadros de diálogo de configuración deben ser Singletons y adopté el patrón anterior. Desafortunadamente, la clase principal de Qt, “QApplication”, se asignó en la stack en la función “principal”, y Qt prohíbe crear / destruir diálogos cuando no hay ningún objeto de aplicación disponible.

Es por eso que prefiero los singletons asignados en el montón. Proporciono métodos explícitos de “init ()” y “term ()” para todos los singletons y los llamo dentro de “main”. Por lo tanto, tengo un control total sobre el orden de creación / destrucción de singletons, y también garantizo que se crearán singletons, sin importar si alguien llamó “getInstance ()” o no.

Aquí hay una implementación fácil.

 #include  #include  using namespace std; class SingletonClass { public: static SingletonClass* getInstance() { return (!m_instanceSingleton) ? m_instanceSingleton = new SingletonClass : m_instanceSingleton; } private: // private constructor and destructor SingletonClass() { cout << "SingletonClass instance created!\n"; } ~SingletonClass() {} // private copy constructor and assignment operator SingletonClass(const SingletonClass&); SingletonClass& operator=(const SingletonClass&); static SingletonClass *m_instanceSingleton; }; SingletonClass* SingletonClass::m_instanceSingleton = nullptr; int main(int argc, const char * argv[]) { SingletonClass *singleton; singleton = singleton->getInstance(); cout << singleton << endl; // Another object gets the reference of the first object! SingletonClass *anotherSingleton; anotherSingleton = anotherSingleton->getInstance(); cout << anotherSingleton << endl; Sleep(5000); return 0; } 

Solo se crea un objeto creado y esta referencia de objeto todas y cada una de las palabras posteriores.

 SingletonClass instance created! 00915CB8 00915CB8 

Aquí 00915CB8 es la ubicación de la memoria del objeto Singleton, el mismo para la duración del progtwig pero (normalmente) diferente cada vez que se ejecuta el progtwig.

NB: no es seguro para subprocesos. Debe garantizar la seguridad del hilo.

De hecho, es probable que esté asignado desde el montón, pero sin las fonts no hay forma de saberlo.

La implementación típica (tomada de algún código que ya tengo en emacs) sería:

 Singleton * Singleton::getInstance() { if (!instance) { instance = new Singleton(); }; return instance; }; 

… y confía en que el progtwig salga del scope para limpiarlo después.

Si trabaja en una plataforma donde la limpieza debe hacerse manualmente, probablemente agregue una rutina de limpieza manual.

Otro problema al hacerlo de esta manera es que no es seguro para subprocesos. En un entorno multiproceso, dos subprocesos podrían pasar por el “si” antes de que cualquiera tenga la oportunidad de asignar la nueva instancia (por lo que ambos lo harían). Esto aún no es demasiado importante si confía en la finalización del progtwig para limpiar de todos modos.

Se trata de la gestión de objetos a lo largo de la vida. Supongamos que tiene más de un single en su software. Y ellos dependen del singleton de Logger. Durante la destrucción de la aplicación, supongamos que otro objeto singleton usa Logger para registrar sus pasos de destrucción. Debes garantizar que Logger debería ser limpiado al último. Por lo tanto, también vea este artículo: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf

No encontré una implementación de CRTP entre las respuestas, así que aquí está:

 template class Singleton { public: Singleton() = delete; Singleton(const Singleton &) = delete; Singleton &operator=(const Singleton &) = delete; static HeirT &instance() { static HeirT instance; return instance; } }; 

Para usar solo hereda tu clase de esto, como: class Test : public Singleton

¿Alguien ha mencionado std::call_once y std::once_flag ? La mayoría de los otros enfoques, incluido el doble locking verificado, están rotos.

Un problema importante en la implementación de patrones únicos es la inicialización segura. La única forma segura es proteger la secuencia de inicialización con barreras de sincronización. Pero esas barreras necesitan ser iniciadas de manera segura. std::once_flag es el mecanismo para obtener una inicialización segura garantizada.

 #define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;} 

Ejemplo:

  class CCtrl { private: CCtrl(void); virtual ~CCtrl(void); public: INS(CCtrl); 

Además de la otra discusión aquí, puede valer la pena señalar que puede tener globalidad, sin limitar el uso a una instancia. Por ejemplo, considere el caso de la referencia contando algo …

 struct Store{ std::array data; size_t get(size_t idx){ /* ... */ } void incr_ref(size_t idx){ /* ... */} void decr_ref(size_t idx){ /* ... */} }; template struct ItemRef{ size_t idx; auto get(){ return store_p->get(idx); }; ItemRef() { store_p->incr_ref(idx); }; ~ItemRef() { store_p->decr_ref(idx); }; }; Store store1_g; Store store2_g; // we don't restrict the number of global Store instances 

Ahora en algún lugar dentro de una función (como main ) puedes hacer:

 auto ref1_a = ItemRef<&store1_g>(101); auto ref2_a = ItemRef<&store2_g>(201); 

Los refs no necesitan almacenar un puntero en sus respectivas Store porque esa información se proporciona en tiempo de comstackción. Tampoco tiene que preocuparse por la duración de la Store porque el comstackdor requiere que sea global. Si de hecho solo hay una instancia de Store entonces no hay gastos generales en este enfoque; con más de una instancia, depende del comstackdor ser inteligente sobre la generación de código. Si es necesario, la clase ItemRef incluso puede convertirse en friend de la Store (¡puedes tener amigos con plantillas!).

Si Store es una clase de plantilla, las cosas se complican, pero aún es posible utilizar este método, quizás implementando una clase auxiliar con la siguiente firma:

 template  struct StoreWrapper{ /* stuff to access store_p, eg methods returning instances of ItemRef. */ }; 

El usuario ahora puede crear un tipo StoreWrapper (y una instancia global) para cada instancia de Store global, y siempre acceder a las tiendas a través de su instancia de contenedor (olvidando así los detalles sangrientos de los parámetros de plantilla necesarios para usar Store ).

Creo que deberías escribir una función estática en la que se elimine tu objeto estático. Debe llamar a esta función cuando esté a punto de cerrar su aplicación. Esto asegurará que no tengas memory leaks.

El documento al que se ha vinculado anteriormente describe la deficiencia del locking comprobado doble es que el comstackdor puede asignar la memoria para el objeto y establecer un puntero a la dirección de la memoria asignada, antes de que se haya llamado al constructor del objeto. Sin embargo, es bastante fácil en C ++ usar asignadores para asignar la memoria de forma manual, y luego usar una llamada de construcción para inicializar la memoria. Usando este appraoch, el locking con doble verificación funciona bien.

¿Qué tal si usas una ubicación nueva como esta?

 class singleton { static singleton *s; static unsigned char *buffer[sizeof(singleton)/4 *4] //4 byte align static singleton* getinstance() { if (s == null) { s = new(buffer) singleton; } return s; } };