patrones patron ejercicios ejemplos ejemplo diseño desventajas con c++ design-patterns singleton

patron - C++ patrón de diseño Singleton



patron singleton php (18)

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

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

Recientemente me he topado con una realización / implementación del patrón de diseño 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 declaración puedo deducir que el campo de instancia se inicia en el montón. Eso significa que hay una asignación de memoria. ¿Qué es completamente incierto para mí cuándo va a desasignarse exactamente la memoria? ¿O hay un error y la pérdida de memoria? Parece que hay un problema en la implementación.

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


¿Qué hay de usar la colocació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; } };


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

struct Store{ std::array<Something, 1024> data; size_t get(size_t idx){ /* ... */ } void incr_ref(size_t idx){ /* ... */} void decr_ref(size_t idx){ /* ... */} }; template<Store* store_p> 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 a su Store respectiva porque esa información se proporciona en tiempo de compilación. Tampoco tiene que preocuparse por la vida útil de la Store porque el compilador requiere que sea global. Si de hecho solo hay una instancia de Store , no hay sobrecarga en este enfoque; con más de una instancia, depende del compilador ser inteligente en la generación de código. Si es necesario, la clase ItemRef puede incluso ser un friend de la Store (¡puedes tener amigos de plantilla!).

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

template <typename Store_t, Store_t* store_p> struct StoreWrapper{ /* stuff to access store_p, e.g. methods returning instances of ItemRef<Store_t, store_p>. */ };

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


Al ser un Singleton, normalmente no quieres que se destruya.

Se demolerá y se desasignará cuando finalice el programa, 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 permita restaurarlo a un estado limpio, y reasignarlo la próxima vez que se use, pero eso está fuera del alcance de un Singleton "clásico".


Aquí hay una implementación fácil.

#include <Windows.h> #include <iostream> 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 y esta referencia de objeto se devuelve cada vez que sigue las contraseñas.

SingletonClass instance created! 00915CB8 00915CB8

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

NB Esta no es una opción segura para subprocesos. Debe garantizar la seguridad de los hilos.


Clase de singleton simple, este debe ser su archivo de clase de encabezado

#ifndef SC_SINGLETON_CLASS_H #define SC_SINGLETON_CLASS_H class SingletonClass { public: static SingletonClass* Instance() { static SingletonClass* instance = new SingletonClass(); return instance; } void Relocate(int X, int Y, int Z); private: SingletonClass(); ~SingletonClass(); }; #define sSingletonClass SingletonClass::Instance() #endif

Accede a tu singleton de esta manera:

sSingletonClass->Relocate(1, 2, 5);


Creo que deberías escribir una función estática donde tu objeto estático sea eliminado. Debería llamar a esta función cuando esté a punto de cerrar su aplicación. Esto asegurará que no tenga pérdida de memoria.


De hecho, es probable que se asigne del montón, pero sin las fuentes 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íe en que el programa salga de su alcance para limpiarse después.

Si trabaja en una plataforma en la que la limpieza se debe realizar manualmente, probablemente agregaría una rutina de limpieza manual.

Otro problema con hacerlo de esta manera es que no es seguro para subprocesos. En un entorno de multiproceso, dos subprocesos podrían pasar el "si" antes de que cualquiera de los dos tenga la oportunidad de asignar la nueva instancia (así que ambos lo harían). Esto todavía no es un problema demasiado grande si confía en la finalización del programa para la limpieza de todos modos.


El documento al que se vinculó anteriormente describe la deficiencia del doble control de bloqueo es que el compilador puede asignar la memoria para el objeto y establecer un puntero a la dirección de la memoria asignada, antes de que se llame al constructor del objeto. Sin embargo, en c ++ es bastante fácil usar asignadores para asignar la memoria manualmente y luego usar una llamada de construcción para inicializar la memoria. Usando este enfoque, el bloqueo de doble comprobación funciona bien.


En 2008 proporcioné una implementación en C ++ 98 del patrón de diseño Singleton que se evalúa de forma perezosa, se garantiza la destrucción, no es técnicamente seguro para subprocesos:
¿Puede alguien 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 perezosa, se destruye correctamente y thread-safe .

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 };

Consulte este artículo sobre cuándo usar un singleton: (no a menudo)
Singleton: ¿Cómo se debe utilizar?

Vea estos dos artículos sobre el orden de inicialización y cómo hacer frente:
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 tiempos de vida:
¿Cuál es el tiempo de vida de una variable estática en una función de C ++?

Vea este artículo que discute algunas implicaciones de subprocesos para singletons:
La instancia de Singleton declarada como variable estática del método GetInstance, ¿es segura para subprocesos?

Vea este artículo que explica por qué el bloqueo de doble comprobación no funcionará en C ++:
¿Cuáles son todas las conductas indefinidas comunes que un programador de C ++ debe conocer?
Dr Dobbs: C ++ y los peligros del bloqueo de doble control: Parte I


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

Me encontré con este problema, al intentar introducir un Singleton en la aplicación Qt. Decidí que todos mis cuadros de diálogo de configuración deben ser Singletons y adopté el patrón anterior. Desafortunadamente, la clase principal de Qt, QApplication fue asignada en la pila en la función main , y Qt prohíbe la creación / destrucción de diálogos cuando no hay un objeto de aplicación disponible.

Es por eso que prefiero singletons asignados en montón. Proporciono métodos de init() y term() explícitos 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 llamado getInstance() o no.


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

template<typename HeirT> 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<Test>


Otra alternativa de no asignación: cree un singleton, digamos de la clase C , según lo necesite:

singleton<C>()

utilizando

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

Ni esta ni la respuesta de Cătălin es segura para subprocesos en C ++ actual, pero estará en C ++ 0x.


Se podría evitar la asignación de memoria. Hay muchas variantes, todas con problemas en el caso de un entorno multihilo.

Prefiero este tipo de implementación (en realidad, no se dice correctamente que prefiero, porque evito los singleton 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.


Se trata de la gestión de la vida útil del objeto. Supongamos que tiene más de singletons en su software. Y dependen de Logger Singleton. Durante la destrucción de la aplicación, suponga que otro objeto Singleton utiliza Logger para registrar sus pasos de destrucción. Tienes que garantizar que el Logger debe limpiarse en último lugar. Por lo tanto, también puede consultar este documento: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf


Si desea asignar el objeto en el montón, ¿por qué no usar un puntero único. La memoria también se desasignará 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<S> m_s; S(); S(S const&); // Don''t Implement void operator=(S const&); // Don''t implement }; std::unique_ptr<S> S::m_s(0);


La respuesta de @Loki Astari es excelente.

Sin embargo, hay veces con múltiples objetos estáticos en los que necesita poder garantizar que el singleton no se destruirá hasta que todos los objetos estáticos que usan el singleton ya no lo necesiten.

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

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


#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);