Recientemente me topé con la realización / implementación del patrón de diseño Singleton para C ++. Se ve 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. Lo que no está completamente claro para mí es cuándo se va a desasignar exactamente la memoria. ¿O hay un error y 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?
c++
design-patterns
singleton
Artem Barger
fuente
fuente
Respuestas:
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:
¿alguien puede proporcionarme una muestra de Singleton en c ++?
Aquí hay una implementación actualizada de C ++ 11 del patrón de diseño Singleton que se evalúa de forma diferida, se destruye correctamente y es segura para subprocesos .
Consulte este artículo sobre cuándo usar un singleton: (no es frecuente)
Singleton: cómo debe usarse
Consulte estos dos artículos sobre el orden de inicialización y cómo hacer frente:
Orden de inicialización de variables estáticas
Búsqueda de problemas de orden de inicialización estática de C ++
Consulte este artículo que describe la vida útil:
¿Cuál es la vida útil de una variable estática en una función C ++?
Consulte este artículo que analiza algunas implicaciones de subprocesos para singletons:
instancia Singleton declarada como variable estática del método GetInstance, ¿es segura para subprocesos?
Consulte este artículo que explica por qué el bloqueo de doble verificación no funcionará en C ++:
¿Cuáles son todos los comportamientos indefinidos comunes que un programador de C ++ debe conocer?
Dr. Dobbs: C ++ y los peligros del bloqueo doblemente verificado: Parte I
fuente
What irks me most though is the run-time check of the hidden boolean in getInstance()
Esa es una suposición sobre la técnica de implementación. No tiene por qué suponerse que esté vivo. consulte stackoverflow.com/a/335746/14065 . Puede forzar una situación para que siempre esté viva (menos sobrecarga queSchwarz counter
). Las variables globales tienen más problemas con el orden de inicialización (en todas las unidades de compilación) ya que no fuerza un orden. La ventaja de este modelo es 1) inicialización diferida. 2) Capacidad para hacer cumplir una orden (Schwarz ayuda pero es más feo). Sí,get_instance()
es mucho más feo.Al ser un Singleton, generalmente no desea que se destruya.
Será derribado y desasignado cuando el programa finalice, 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 lo use, pero eso está fuera del alcance de un singleton "clásico".
fuente
Podrías evitar la asignación de memoria. Hay muchas variantes, todas tienen problemas en caso de entorno de subprocesos múltiples.
Prefiero este tipo de implementación (en realidad, no se dice correctamente que prefiero, porque evito los singletons tanto como sea posible):
No tiene asignación de memoria dinámica.
fuente
La respuesta de @Loki Astari es excelente.
Sin embargo, hay veces con estática varios objetos donde tiene que ser capaz de garantizar que el producto único no será destruido hasta que todos los objetos estáticos que utilizan el producto único ya no lo necesitan.
En este caso,
std::shared_ptr
se puede usar para mantener vivo el singleton para todos los usuarios, incluso cuando se llama a los destructores estáticos al final del programa:fuente
Otra alternativa sin asignación: cree un singleton, digamos de clase
C
, según lo necesite:utilizando
Ni esta ni la respuesta de Cătălin es automáticamente segura para subprocesos en C ++ actual, pero estará en C ++ 0x.
fuente
No encontré una implementación CRTP entre las respuestas, así que aquí está:
Para usar simplemente hereda tu clase de esto, como:
class Test : public Singleton<Test>
fuente
La solución en la respuesta aceptada tiene un inconveniente significativo: se llama al destructor para el singleton después de que el control abandona el
main()
función. Puede haber problemas realmente, cuando algunos objetos dependientes se asignan dentromain
.Encontré 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
se asignó en la pila en elmain
función, 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 al montón. Proporciono un método explícito
init()
yterm()
para todos los singletons y los llamo dentromain
. 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.fuente
Aquí hay una implementación fácil.
Solo se crea un objeto y esta referencia de objeto se devuelve todas y cada una de las palabras posteriores.
Aquí 00915CB8 es la ubicación de memoria del Objeto singleton, la misma durante la duración del programa pero (¡normalmente!) Diferente cada vez que se ejecuta el programa.
Nota: este no es un hilo seguro. Debe garantizar la seguridad del hilo.
fuente
Si desea asignar el objeto en el montón, ¿por qué no utilizar un puntero único? La memoria también se desasignará ya que estamos usando un puntero único.
fuente
m_s
un local destatic
degetInstance()
e inicializar inmediatamente sin una prueba.De hecho, probablemente se asigna desde el 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:
... y confíe en que el programa se salga del alcance para limpiar después.
Si trabaja en una plataforma donde la limpieza debe realizarse manualmente, probablemente agregaría 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 todavía no es un gran problema si confía en la finalización del programa para limpiar de todos modos.
fuente
¿Alguien ha mencionado
std::call_once
ystd::once_flag
? La mayoría de los otros enfoques, incluido el bloqueo de doble verificación, están rotos.Un problema importante en la implementación del patrón singleton es la inicialización segura. La única forma segura es proteger la secuencia de inicialización con barreras de sincronización. Pero esas barreras en sí mismas deben iniciarse de manera segura.
std::once_flag
es el mecanismo para garantizar una inicialización segura garantizada.fuente
Repasamos este tema recientemente en mi clase de EECS. Si desea ver las notas de clase en detalle, visite http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf
Hay dos maneras en que sé crear una clase Singleton correctamente.
Primera forma:
Impleméntelo de forma similar a como lo tiene en su ejemplo. En cuanto a la destrucción, "los Singletons generalmente perduran durante la ejecución del programa; la mayoría de los sistemas operativos recuperarán la memoria y la mayoría de los otros recursos cuando finalice un programa, por lo que hay un argumento para no preocuparse por esto".
Sin embargo, es una buena práctica limpiar al finalizar el programa. Por lo tanto, puede hacer esto con una clase auxiliar SingletonDestructor estática auxiliar y declarar eso como amigo en su Singleton.
Singleton_destroyer se creará al inicio del programa, y "cuando el programa finaliza, todos los objetos globales / estáticos son destruidos por el código de apagado de la biblioteca en tiempo de ejecución (insertado por el enlazador), por lo que el destructor eliminará el Singleton, ejecutando su incinerador de basuras."
Segunda forma
Esto se llama Meyers Singleton, creado por el asistente de C ++ Scott Meyers. Simplemente defina get_instance () de manera diferente. Ahora también puede deshacerse de la variable miembro del puntero.
Esto es correcto porque el valor devuelto es por referencia y puede usar la
.
sintaxis en lugar de->
acceder a las variables miembro."El compilador crea automáticamente el código que crea 's' por primera vez a través de la declaración, no después, y luego elimina el objeto estático al finalizar el programa".
Tenga en cuenta también que con el Singleton de Meyers "puede entrar en una situación muy difícil si los objetos dependen unos de otros en el momento de la terminación: ¿cuándo desaparece el Singleton en relación con otros objetos? Pero para aplicaciones simples, esto funciona bien".
fuente
Además de la otra discusión aquí, vale la pena señalar que puede tener globalidad, sin limitar el uso a una instancia. Por ejemplo, considere el caso de referencia contando algo ...
Ahora, en algún lugar dentro de una función (como
main
) puede hacer:Los árbitros no necesitan almacenar un puntero en sus respectivos
Store
porque esa información se proporciona en tiempo de compilación. Tampoco tiene que preocuparse por laStore
vida útil del compilador porque el compilador requiere que sea global. Si de hecho solo hay una instancia,Store
entonces no hay sobrecarga en este enfoque; con más de una instancia, depende del compilador ser inteligente sobre la generación de código. Si es necesario, laItemRef
clase incluso se puede hacer unfriend
deStore
(¡puedes tener amigos con plantilla!).Si en
Store
sí es una clase con plantilla, las cosas se vuelven más complicadas, pero aún es posible usar este método, tal vez implementando una clase auxiliar con la siguiente firma:El usuario ahora puede crear un
StoreWrapper
tipo (y una instancia global) para cadaStore
instancia 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 usarStore
).fuente
Se trata de la gestión del tiempo de vida 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 usa Logger para registrar sus pasos de destrucción. Debe garantizar que Logger se debe limpiar en último lugar. Por lo tanto, consulte también este documento: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf
fuente
Mi implementación es similar a la de Galik. La diferencia es que mi implementación permite que los punteros compartidos limpien la memoria asignada, en lugar de retener la memoria hasta que la aplicación salga y los punteros estáticos se limpien.
fuente
Su código es correcto, excepto que no declaró el puntero de instancia fuera de la clase . Las declaraciones de clase estática de variables estáticas no se consideran declaraciones en C ++, sin embargo, esto está permitido en otros lenguajes como C # o Java, etc.
Debe saber que la instancia de Singleton no necesita ser eliminada manualmente por nosotros . Necesitamos un solo objeto en todo el programa, por lo que al final de la ejecución del programa, se desasignará automáticamente.
fuente
El documento al que se enlazó anteriormente describe la deficiencia del bloqueo de doble verificación 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, es bastante fácil en c ++ usar asignadores para asignar la memoria manualmente, y luego usar una llamada de construcción para inicializar la memoria. Usando este appraoch, el bloqueo de doble verificación funciona bien.
fuente
Ejemplo:
fuente
Clase singleton simple, este debe ser su archivo de clase de encabezado
Acceda a su singleton de esta manera:
fuente
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 tenga pérdidas de memoria.
fuente