2009-09-23 17 views
45

Un amigo mío me preguntó hoy por qué debería preferir el uso de singleton sobre el objeto estático global? La forma en que lo comencé a explicar fue que el singleton puede tener estado vs. objeto estático global no ... pero no estaba seguro ... porque esto en C++ ... (Venía de C#)C++ singleton vs. objeto estático global

¿Cuáles son las ventajas una sobre la otra? (en C++)

Respuesta

21

En C++, el orden de instanciación de objetos estáticos en diferentes unidades de compilación no está definido. Por lo tanto, es posible que un mundo haga referencia a otro que no está construido, haciendo explotar su programa. El patrón singleton elimina este problema al vincular la construcción a una función miembro estática o función libre.

Hay un resumen decente here.

+0

y luego introduce condiciones de carrera complejas y descarta la seguridad del hilo porque dos hilos pueden llamar a esa función simultáneamente. – jalf

+1

Curiosamente, std :: cout parece funcionar bien, a pesar de ser un simple objeto global antiguo ... Estoy seguro de que si el orden de inicialización fuera un problema tan grande, el comité estándar ya lo habría notado.Solo es un problema si usa en exceso los globales y hace que dependan unos de otros. – jalf

+1

@jalf: Haz tu tarea. 'cout' y' cin' se especifican específicamente para ser construidos antes de 'main()' por el estándar. Otros objetos globales no reciben este tratamiento. Además, existen implementaciones de singleton seguras para hilos y no es difícil escribir usted mismo; solo necesita un doble candado para evitar la doble construcción. Personalmente trato de evitar singletons, creo que el patrón se usa en exceso. Pero tu falta de conocimiento de C++ no es motivo para menospreciarme. – rlbond

52

En realidad, en C++ la forma preferida es el objeto estático local.

Printer & thePrinter() { 
    static Printer printer; 
    return printer; 
} 

Esto es técnicamente un singleton sin embargo, esta función puede ser incluso un método estático de una clase. Por lo tanto, se garantiza que se construirá antes que se use a diferencia de los objetos estáticos globales, que se pueden crear en cualquier orden, lo que hace posible que falle de forma inconsistente cuando un objeto global usa otro escenario bastante común.

Lo que lo hace mejor que una forma común de hacer singletons con la creación de una nueva instancia llamando al new es que se llamará al destructor de objetos al final de un programa. No sucederá con singleton dinámicamente asignado.

Otro lado positivo es que no hay forma de acceder a singleton antes de que se cree, incluso desde otros métodos estáticos o desde subclases. Le ahorra tiempo de depuración.

+7

Esta es la mejor manera de hacer singletons. Usar nuevo no es común en el código real, aunque hay muchos ejemplos en Internet que parecen mostrar que es una buena idea (no les creo). –

+3

En realidad, no hay una "mejor" forma de hacer singletons. Si este singleton hace referencia a otro singleton en su destructor, no funcionará correctamente. – rlbond

+2

Tenga en cuenta que esto generalmente no es seguro para subprocesos (depende de la implementación: por ejemplo, g ++ insertará bloqueos, pero MSVC no lo hará). –

3

Motivo 1:
Los singleton son fáciles de hacer, por lo que son de fabricación perezosa.
Si bien puede hacer esto con globales, el desarrollador debe realizar un trabajo adicional. Entonces, por defecto, los globales siempre se inicializan (aparte de algunas reglas especiales con espacios de nombres).

Si su objeto es grande y/o costoso de construir, puede que no desee construirlo a menos que realmente tenga que usarlo.

Motivo 2:
Orden del problema de inicialización (y destrucción).

GlobalRes& getGlobalRes() 
{ 
    static GlobalRes instance; // Lazily initialized. 
    return instance; 
} 


GlobalResTwo& getGlobalResTwo() 
{ 
    static GlobalResTwo instance; // Lazy again. 
    return instance; 
} 


// Order of destruction problem. 
// The destructor of this object uses another global object so 
// the order of destruction is important. 
class GlobalResTwo 
{ 
    public: 
     GlobalResTwo() 
     { 
      getGlobalRes(); 
      // At this point globalRes is fully initialized. 
      // Because it is fully initialized before this object it will be destroyed 
      // after this object is destroyed (Guaranteed) 
     } 
     ~GlobalResTwo() 
     { 
      // It is safe to use globalRes because we know it will not be destroyed 
      // before this object. 
      getGlobalRes().doStuff(); 
     } 
}; 
2

Usando Singleton ("construcción en el primer uso") modismo, puede evitar static initialization order fiasco

+1

Ese artículo es terrible. La solución propuesta es tan mala como el problema. Al usar new en la función, nunca llamarás al destructor. –

+1

Esta es una de las pocas situaciones en las que las pérdidas de memoria son correctas, ya que el programa finaliza de todos modos (a menos que tenga un SO verdaderamente terrible que de alguna manera no reclame la memoria). Si lees, la próxima pregunta frecuente, explica por qué. – coppro

+1

Lea las secciones 10.12 a 10.16 –

2

Otro de los beneficios de la Singleton sobre el objeto estático global es que debido a que el constructor es privado, hay una muy clara , directiva obligatoria del compilador que dice "Solo puede haber una".

En comparación, con el objeto estático global, no habrá nada que detenga la creación de un código de desarrollador que crea una instancia adicional de este objeto.

El beneficio de la restricción adicional es que tiene una garantía de cómo se usará el objeto.

+1

"directivo forzado por el compilador que dice" Solo puede haber uno "" ¿Tiene cuidado de encontrar un caso de uso único en el que se trata de algo ** bueno **? No es un beneficio agregar restricciones arbitrarias e innecesarias a su código.Es un beneficio si su código es * general * y * reutilizable *, y no será eso si lo primero que hace es darle palmadas en él que no necesita. – jalf

+0

@jalf: la mayoría del código debe ser general y reutilizable. Pero en una aplicación grande puede tener varios objetos que requieren alguna referencia central: el marco de aplicación global. Un ejemplo sería algún tipo de administrador de bloqueos. Las diferentes partes de la aplicación que crean sus propios administradores de bloqueo serían inapropiadas. –

+0

No, puede tener fácilmente diferentes administradores de bloqueos para diferentes componentes. – jalf

0

En C++, no hay una gran diferencia entre los dos en términos de utilidad real. Un objeto global puede, por supuesto, mantener su propio estado (posiblemente con otras variables globales, aunque no lo recomiendo).Si va a utilizar un global o un singleton (y existen muchas razones para no hacerlo), la razón principal para usar un singleton sobre un objeto global es que con un singleton, puede tener un polimorfismo dinámico al heredar varias clases de una clase base singleton.

+0

¿Cómo se implementaría este polimorfismo y cuáles son algunos casos de uso? –

-1

OK, hay dos razones para ir con un singleton realmente. Una es el orden estático de lo que todos hablan.

El otro es para evitar que alguien de hacer algo como esto cuando se utiliza el código:

CoolThing blah; 
gs_coolGlobalStaticThing = blah; 

o, aún peor:

gs_coolGlobalStaticThing = {}; 

El aspecto encapsulación protegerá a su instancia de los idiotas y malicioso idiotas.

+0

Estás hablando de accesadores tipo ... –

7

Un amigo mío me preguntó hoy por qué debería preferir el uso de singleton sobre el objeto estático global? La forma en que lo comencé a explicar fue que el singleton puede tener estado vs. objeto estático global no ... pero no estaba seguro ... porque esto en C++ ... (venía de C#)

Un objeto global estática puede tener estado en C#, así:

class myclass { 
// can have state 
// ... 
public static myclass m = new myclass(); // globally accessible static instance, which can have state 
} 

¿Cuáles son las ventajas uno sobre el otro? (en C++)

Un singleton paraliza su código, una instancia global no estática. Hay innumerables preguntas sobre SO sobre los problemas con los singletons. Here's one, and another, or another.

En resumen, un producto único le da dos cosas:

  • un objeto accesible a nivel mundial, y
  • una garantía de que sólo una instancia puede ser creado.

Si queremos solo el primer punto, debemos crear un objeto accesible a nivel mundial. Y ¿por qué querríamos el segundo? Nosotros no conocemos de antemano cómo nuestro código puede ser utilizado en el futuro, entonces, ¿por qué eliminarlo y eliminar lo que puede ser una funcionalidad útil? Usualmente somos mal cuando predecimos que "solo necesitaré una instancia". Y hay una gran diferencia entre "Solo necesitaré una instancia" (la respuesta correcta es create una instancia), y "la aplicación no se puede ejecutar bajo ninguna circunstancia si se crea más de una instancia. bloqueo, formatee el disco duro del usuario y publique datos confidenciales en Internet "(la respuesta aquí es: lo más probable es que su aplicación esté rota, pero si no es así, entonces sí, es lo único que necesita)

+0

Existen algunos usos legítimos para el patrón singleton. Se usa en exceso, pero hay algunos casos en los que está bien (algunos ejemplos: un RNG global para un juego. Objeto de opciones de configuración. Controlador de grupo de memoria) – rlbond

+0

Y como dije anteriormente, el orden de construcción de objetos en diferentes unidades de compilación no está definido . No me creíste y sacaste 'cout' y' cin', pero estos son casos especiales definidos por el estándar. – rlbond

+0

Hay pocas razones por las cuales un juego debería usar un único RNG global. ¿Por qué no reducir la contención y tener una por hilo? Y es posible que incluso desee una diferente por subsistema. O por jugador, en un escenario de varios jugadores. Opciones de configuración? Puedo ver un uso para dos fácilmente. Tiene un conjunto de configuraciones que están activas, y un segundo conjunto que el usuario está modificando actualmente, pero que aún no ha aplicado. Puede ** fácilmente ** tener más de un grupo de memoria. Por supuesto, * hay * usos legítimos para el patrón singleton. Pero son extremadamente raros, y los que enumeró son * no * entre ellos. – jalf