2009-09-08 12 views
16

El otro día he visto código que usa el llamado patrón singleton. Es decir algo en la línea deSingleton - ¿Por qué usar clases?

class MySingleton{ 
public: 
    void foo() { ... } 
    static MySingleton&get_instance(){ 
     static MySingleton singleton; 
     return singleton 
    } 
private: 
    MySingleton(){ ... } 
    ~MySingleton(){ ... } 
    int bar; 
}; 

me veo por qué uno querría hacer eso:

  • Haga la instancia accesible a nivel mundial.
  • Asegúrese de que nunca haya más de una instancia de esa clase.

Sin embargo, no veo por qué esta forma de hacer las cosas es superior a un par de funciones gratuitas. La forma en que me ponerlo en práctica es poner

namespace some_name{ 
    void foo(); 
} 

en la cabecera y

namespace some_name{ 
    void foo(){ 
     ... 
    } 
} 

en el archivo de implementación. Si necesito inicialización y/o limpieza que sea añadir un par de funciones que debe ser llamada explícita o añado

namespace{ 
    class Dummy{ 
     Dummy(){ ... } 
     ~Dummy(){ ... } 
    }dummy; 
} 

en el archivo de implementación.

Sé que esto es desde un punto de vista semántico un singleton, sin embargo, veo la primera variante utilizada con mucha más frecuencia en C++ Code que la segunda. ¿Por qué? Considero que la segunda versión es ligeramente superior, por lo que me pregunto si me falta algo obvio.

  • La segunda versión es más sencilla de implementar y menos propensa a errores. En la primera variante, el constructor de copia privada falta a propósito para demostrar esto. En la segunda variante no hay forma de hacer este error.
  • La implementación y la interfaz están mejor separadas en la segunda versión. En el primero, todos los miembros privados deben declararse en el encabezado. Esto tiene la ventaja de que puede reescribir la implementación desde cero y ni siquiera necesita recompilar nada que use el singleton. Cuando se usa la primera variante, es muy probable que tenga que volver a compilar todo el código de usuario incluso cuando solo se modifican los detalles de la implementación.
  • Los detalles de implementación están ocultos en ambos casos. En la primera variante, uso privado y, en la segunda, uso de espacios de nombres sin nombre.

¿Puede explicarme por qué todos usan la primera variante? No veo una sola ventaja sobre la buena forma de hacer las cosas en C.

+0

No necesita un constructor de copia en el patrón singleton. El propósito del patrón singleton es asegurarse de que se pueda crear una única instancia de la clase. – erelender

+4

Cuando se discuten singletons, es necesario agregar que los singletons son un patrón de diseño * malo * y deben evitarse por las razones que se mencionan aquí: http://stackoverflow.com/questions/1392315/problems-with-singleton-pattern –

+5

fanáticos son tan divertido –

Respuesta

4

¿Esto ayuda?

What is so bad about singletons? http://steve.yegge.googlepages.com/singleton-considered-stupid

reformulado: Un producto único es un glorificado mundial, por lo que sólo 'poner en práctica' como global.

+1

Supongamos que el singleton abre un archivo de configuración o una secuencia de registro, id. ¿Recuerda llamar a la función de configuración global antes de usar por primera vez el registro o la configuración? –

+0

@MGB sigue siendo una clase con ctors y cosas –

+0

No realmente. Se pierde el sentido de mi pregunta. Me refiero a la forma de implementar un singleton en C++. No a la pregunta si uno debería usar uno en primer lugar. –

3

Se llama a la construcción de static MySingleton singleton; en el primer uso. (Cuando se llama al get_instance()). Si nunca se llama, nunca llama al constructor. Su método llamará al constructor en tiempo de construcción estático. El método anterior permite el orden y el tiempo de los constructores a los que se llama. Su método ordenará la construcción de cada singleton según el orden de inicialización estático del compilador.

+0

Es la implementación definida cuando se ejecuta el constructor de variables estáticas locales. Usando la segunda variante, tienes un comportamiento confiable. También puede agregar una bandera y obtener el comportamiento perezoso de una manera portátil. –

+2

Es la implementación definida cuando se ejecuta el constructor de variables estáticas locales. -> No es verdadero cuando están en una función o en una sentencia if, etc ... La memoria se puede asignar en cualquier momento antes de main() pero el constructor de estos tipos de estática se invoca cuando el puntero del programa lo pasa por primera vez. Cuando se definen fuera de una función de algún tipo, cuando se llama al constructor es específico de la implementación. –

+0

@Ben. El orden de evaluación de las variables de función estática está bien definido. Está en el punto de primer uso. Por lo tanto, le da el equivalente del señalado que sugiere, pero lo hace el compilador (por lo tanto, menos posibilidades de error (sin decir que compielr es perfecto pero generalmente es mejor que un humano)) (también en gcc es seguro para subprocesos, y con suerte estándar en la próxima versión de C++). –

7

De acuerdo con la línea del partido (E. Gamma, R. Helm, R. Johnson y J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, Reading, MA, 1995, página 128), el singleton ofrece las siguientes ventajas sobre la solución que propones

  • Puede refinar las operaciones y la representación, p. a través de subclases.
  • Puede cambiar de opinión en un momento posterior y tener varias instancias.
  • Puede anular polimorficamente los métodos de Singleton.
  • Puede configurar su aplicación en tiempo de ejecución inicializando la instancia de singleton con la clase que necesita.

Habiendo dicho eso, en la mayoría de los casos considero que la complejidad adicional es excesiva y raramente uso el patrón en el código que escribo. Pero puedo ver su valor cuando diseñas una API que otros usarán.

+0

"Puedes cambiar de opinión en otro momento y tener varias instancias". En la práctica, esto no es cierto porque, por ejemplo, fomenta el código acoplado. –

+1

@Dustin Getz ¿Cómo fomenta el código acoplado más que cualquier otra cosa? – Imagist

+0

Todo esto se puede lograr mediante funciones gratuitas y usando indicadores de función internamente para cambiar la implementación. La única diferencia es la interfaz. es decir, te gusta escribir instance() o no. –

0

El problema con el uso de funciones gratuitas es que no obtienen de forma predeterminada un comportamiento persistente compartido, como su clase singleton puede obtener con las variables miembro y las constantes.

Puede proceder a usar variables globales estáticas para hacer lo mismo, pero para alguien que trata de resolver eso, eso hace que el alcance de lo que tienen que observar comprenda el comportamiento de esas rutinas casi ilimitado. Con una clase singleton, todo está organizado en una clase para que el lector lo examine.

+0

No veo por qué es más difícil aprender a usar variables globales estáticas que no son propensas a errores, que aprender a hacer las cosas vudú propensas a errores necesarias para garantizar que solo exista una instancia. Los principiantes normalmente usan variables globales con demasiada frecuencia. Esto muestra que el concepto de datos globales es bastante intuitivo. –

1

Tener funciones + datos estáticos emular el patrón singleton se basaría en el alcance del archivo de C++ y la compilación separada. Estas son construcciones de compilador en lugar de construcciones de lenguaje. El patrón de clase singleton permite la encapsulación de datos independientemente de la ubicación con respecto a las unidades de compilación; está encapsulado correctamente incluso si está definido en un archivo con otras clases y funciones.

Además, de hecho, no emulará el comportamiento del patrón singleton, sino simplemente el de un objeto estático, que no es lo mismo. La vida útil de un singleton es independiente de la vida del proceso que la contiene. El singleton correctamente formado se instancia al primer uso, mientras que los datos estáticos se instancian e inicializan antes de que se inicie main(). Esto puede ser un problema si, por ejemplo, la construcción del objeto depende de la existencia de alguna otra entidad en tiempo de ejecución. Además, el objeto singleton no ocupa memoria (que no sea su puntero de instancia estática) hasta que se crea una instancia. También se puede destruir y volver a crear en cualquier momento, y de hecho muchas veces.

Tenga en cuenta que si modifica su singleton para proteger el constructor en lugar de hacerlo de forma privada, puede crear una subclase (y así aplicar fácilmente el patrón de reutilización de singleton), no puede hacer eso con un objeto estático o un objeto con estática miembros, o funciones con datos estáticos del archivo, o de cualquier otra manera que pueda tratar de hacerlo correctamente.

No hay nada de malo con su sugerencia per se, siempre que sepa que no es un patrón único y carece de flexibilidad.

0

Personalmente, uso singletons cuando quiero tener control de cuándo se ejecuta el constructor por primera vez.

Por ejemplo; si tengo un singleton para mi sistema de registro, el código para abrir un archivo para escribir se coloca en el constructo singleton, que se llama like; Logger.instance(); en mi proceso de inicio. Si utilizara un espacio de nombre, tendría ese control.