patron singleton c++
Singleton: ¿Cómo se debe utilizar? (24)
- ¿Cómo implementas un Singleton correctamente?
Hay un problema que nunca he visto mencionado, algo que encontré en un trabajo anterior. Teníamos singletons de C ++ que se compartían entre DLL, y la mecánica habitual de garantizar que una única instancia de una clase simplemente no funcionara. El problema es que cada DLL obtiene su propio conjunto de variables estáticas, junto con el EXE. Si su función get_instance está en línea o es parte de una biblioteca estática, cada DLL terminará con su propia copia del "singleton".
La solución es asegurarse de que el código de singleton solo esté definido en una DLL o EXE, o crear un administrador de singleton con esas propiedades para parcelar las instancias.
Edición: De otra pregunta, proporcioné una respuesta que tiene enlaces a muchas preguntas / respuestas sobre singletons: Más información sobre singletons aquí:
Así que he leído el hilo Singletons: ¿buen diseño o una muleta?
Y el argumento sigue rabiando.
Veo Singletons como un patrón de diseño (bueno y malo).
El problema con Singleton no es el patrón, sino los usuarios (lo siento todos). Todos y su padre piensan que pueden implementar uno correctamente (y de las muchas entrevistas que he hecho, la mayoría de la gente no puede). Además, como todos piensan que pueden implementar un Singleton correcto, abusan del Patrón y lo utilizan en situaciones que no son apropiadas (¡reemplazando variables globales con Singletons!).
Así que las principales preguntas que deben ser respondidas son:
- ¿Cuándo debes usar un Singleton?
- ¿Cómo implementas un Singleton correctamente?
Mi esperanza para este artículo es que podamos recopilar juntos en un solo lugar (en lugar de tener que buscar en Google y buscar en varios sitios) una fuente autorizada de cuándo (y luego cómo) usar un Singleton correctamente. También sería apropiada una lista de Anti-Usage e implementaciones incorrectas comunes que expliquen por qué no funcionan y para las implementaciones correctas sus debilidades.
Así que haz rodar la pelota:
Levantaré mi mano y diré que esto es lo que uso pero que probablemente tenga problemas.
Me gusta el manejo del tema por "Scott Myers" en sus libros "Effective C ++"
Buenas situaciones para usar Singletons (no muchas):
- Marcos de registro
- Piscinas de reciclaje de hilo
/*
* C++ Singleton
* Limitation: Single Threaded Design
* See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
* For problems associated with locking in multi threaded applications
*
* Limitation:
* If you use this Singleton (A) within a destructor of another Singleton (B)
* This Singleton (A) must be fully constructed before the constructor of (B)
* is called.
*/
class MySingleton
{
private:
// Private Constructor
MySingleton();
// Stop the compiler generating methods of copy the object
MySingleton(MySingleton const& copy); // Not Implemented
MySingleton& operator=(MySingleton const& copy); // Not Implemented
public:
static MySingleton& getInstance()
{
// The only instance
// Guaranteed to be lazy initialized
// Guaranteed that it will be destroyed correctly
static MySingleton instance;
return instance;
}
};
DE ACUERDO. Vamos a obtener algunas críticas y otras implementaciones juntos.
:-)
A continuación se muestra el mejor enfoque para implementar un patrón de singleton seguro para subprocesos y desasignar la memoria en el propio destructor. Pero creo que el destructor debería ser opcional porque la instancia de singleton se destruirá automáticamente cuando el programa termine:
#include<iostream>
#include<mutex>
using namespace std;
std::mutex mtx;
class MySingleton{
private:
static MySingleton * singletonInstance;
MySingleton();
~MySingleton();
public:
static MySingleton* GetInstance();
MySingleton(const MySingleton&) = delete;
const MySingleton& operator=(const MySingleton&) = delete;
MySingleton(MySingleton&& other) noexcept = delete;
MySingleton& operator=(MySingleton&& other) noexcept = delete;
};
MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton(){ };
MySingleton::~MySingleton(){
delete singletonInstance;
};
MySingleton* MySingleton::GetInstance(){
if (singletonInstance == NULL){
std::lock_guard<std::mutex> lock(mtx);
if (singletonInstance == NULL)
singletonInstance = new MySingleton();
}
return singletonInstance;
}
Con respecto a las situaciones en las que necesitamos usar clases singleton, puede ser: Si queremos mantener el estado de la instancia a lo largo de la ejecución del programa. Si participamos en la escritura en el registro de ejecución de una aplicación donde solo se necesita una instancia del archivo. ser utilizado .... y así sucesivamente. Será apreciable si alguien puede sugerir optimización en mi código anterior.
Anti-uso:
Un problema importante con el uso excesivo de singleton es que el patrón evita la fácil extensión y el intercambio de implementaciones alternativas. El nombre de la clase está codificado de manera rígida donde se usa el singleton.
Básicamente, los Singletons te permiten tener un estado global complejo en idiomas que de otra manera hacen que sea difícil o imposible tener variables globales complejas.
Java en particular usa singletons como un reemplazo para las variables globales, ya que todo debe estar contenido dentro de una clase. Lo más cercano a las variables globales son las variables públicas estáticas, que se pueden usar como si fueran globales con import static
C ++ tiene variables globales, pero el orden en el que se invocan los constructores de variables de clase global es indefinido. Como tal, un singleton le permite diferir la creación de una variable global hasta la primera vez que se necesita esa variable.
Los idiomas como Python y Ruby usan singletons muy poco porque en su lugar puedes usar variables globales dentro de un módulo.
Entonces, ¿cuándo es bueno / malo usar un singleton? Casi exactamente cuando sería bueno / malo usar una variable global.
Como han señalado otros, los principales inconvenientes de los singletons incluyen la incapacidad de extenderlos y la pérdida de poder para instanciar más de una instancia, por ejemplo, con fines de prueba.
Algunos aspectos útiles de singletons:
- instanciación perezosa o por adelantado
- útil para un objeto que requiere configuración y / o estado
Sin embargo, no tiene que usar un singleton para obtener estos beneficios. Puede escribir un objeto normal que haga el trabajo, y luego hacer que las personas accedan a él a través de una fábrica (un objeto separado). La fábrica solo puede preocuparse por crear una instancia y reutilizarla, etc., si es necesario. Además, si programa en una interfaz en lugar de una clase concreta, la fábrica puede usar estrategias, es decir, puede activar y desactivar varias implementaciones de la interfaz.
Finalmente, una fábrica se presta a tecnologías de inyección de dependencia como Spring, etc.
Creo que esta es la versión más robusta para C #:
using System;
using System.Collections;
using System.Threading;
namespace DoFactory.GangOfFour.Singleton.RealWorld
{
// MainApp test application
class MainApp
{
static void Main()
{
LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
LoadBalancer b4 = LoadBalancer.GetLoadBalancer();
// Same instance?
if (b1 == b2 && b2 == b3 && b3 == b4)
{
Console.WriteLine("Same instance/n");
}
// All are the same instance -- use b1 arbitrarily
// Load balance 15 server requests
for (int i = 0; i < 15; i++)
{
Console.WriteLine(b1.Server);
}
// Wait for user
Console.Read();
}
}
// "Singleton"
class LoadBalancer
{
private static LoadBalancer instance;
private ArrayList servers = new ArrayList();
private Random random = new Random();
// Lock synchronization object
private static object syncLock = new object();
// Constructor (protected)
protected LoadBalancer()
{
// List of available servers
servers.Add("ServerI");
servers.Add("ServerII");
servers.Add("ServerIII");
servers.Add("ServerIV");
servers.Add("ServerV");
}
public static LoadBalancer GetLoadBalancer()
{
// Support multithreaded applications through
// ''Double checked locking'' pattern which (once
// the instance exists) avoids locking each
// time the method is invoked
if (instance == null)
{
lock (syncLock)
{
if (instance == null)
{
instance = new LoadBalancer();
}
}
}
return instance;
}
// Simple, but effective random load balancer
public string Server
{
get
{
int r = random.Next(servers.Count);
return servers[r].ToString();
}
}
}
}
Aquí está la versión optimizada para .NET :
using System;
using System.Collections;
namespace DoFactory.GangOfFour.Singleton.NETOptimized
{
// MainApp test application
class MainApp
{
static void Main()
{
LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
LoadBalancer b4 = LoadBalancer.GetLoadBalancer();
// Confirm these are the same instance
if (b1 == b2 && b2 == b3 && b3 == b4)
{
Console.WriteLine("Same instance/n");
}
// All are the same instance -- use b1 arbitrarily
// Load balance 15 requests for a server
for (int i = 0; i < 15; i++)
{
Console.WriteLine(b1.Server);
}
// Wait for user
Console.Read();
}
}
// Singleton
sealed class LoadBalancer
{
// Static members are lazily initialized.
// .NET guarantees thread safety for static initialization
private static readonly LoadBalancer instance =
new LoadBalancer();
private ArrayList servers = new ArrayList();
private Random random = new Random();
// Note: constructor is private.
private LoadBalancer()
{
// List of available servers
servers.Add("ServerI");
servers.Add("ServerII");
servers.Add("ServerIII");
servers.Add("ServerIV");
servers.Add("ServerV");
}
public static LoadBalancer GetLoadBalancer()
{
return instance;
}
// Simple, but effective load balancer
public string Server
{
get
{
int r = random.Next(servers.Count);
return servers[r].ToString();
}
}
}
}
Puedes encontrar este patrón en dotfactory.com .
Dado que un singleton solo permite crear una instancia, controla efectivamente la replicación de la instancia. por ejemplo, no necesitaría varias instancias de una búsqueda; por ejemplo, un mapa de búsqueda Morse, por lo que envolverlo en una clase de singleton es apto. Y solo porque tenga una sola instancia de la clase no significa que también esté limitado en el número de referencias a esa instancia. Puede poner en cola las llamadas (para evitar problemas de subprocesos) a la instancia y efectuar los cambios necesarios. Sí, la forma general de un singleton es pública a nivel mundial, ciertamente puede modificar el diseño para crear un singleton con acceso restringido. No he cansado esto antes, pero sé que es posible. Y para todos aquellos que comentaron que el patrón de singleton es absolutamente malo, debes saber esto: sí, es malo si no lo usas correctamente o dentro de él se limita la funcionalidad efectiva y el comportamiento predecible: no GENERALIZE.
El patrón singleton de Meyers funciona bastante bien la mayor parte del tiempo, y en las ocasiones en que lo hace, no necesariamente paga por buscar algo mejor. Mientras el constructor nunca lance y no haya dependencias entre singletons.
Un singleton es una implementación para un objeto accesible globalmente (GAO a partir de ahora), aunque no todos los GAO son singletons.
Los registradores no deben ser singletes, pero lo ideal sería que los medios de registro sean accesibles a nivel mundial, para desacoplar dónde se genera el mensaje de registro desde dónde o cómo se registra.
La evaluación de carga perezosa / perezosa es un concepto diferente y Singleton generalmente implementa eso también. Viene con muchos de sus propios problemas, en particular, la seguridad de subprocesos y los problemas si falla con excepciones tales que lo que parecía una buena idea en ese momento no es tan bueno después de todo. (Un poco como implementación de COW en cadenas).
Con eso en mente, los GOA se pueden inicializar así:
namespace {
T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;
}
int main( int argc, char* argv[])
{
T1 t1(args1);
T2 t2(args2);
T3 t3(args3);
T4 t4(args4);
pt1 = &t1;
pt2 = &t2;
pt3 = &t3;
pt4 = &t4;
dostuff();
}
T1& getT1()
{
return *pt1;
}
T2& getT2()
{
return *pt2;
}
T3& getT3()
{
return *pt3;
}
T4& getT4()
{
return *pt4;
}
No es necesario hacerlo tan crudamente como eso, y claramente en una biblioteca cargada que contiene objetos, es probable que desee algún otro mecanismo para administrar su vida útil. (Póngalos en un objeto que obtenga cuando cargue la biblioteca).
¿En cuanto a cuando uso singletons? Los usé para 2 cosas: una tabla de singleton que indica qué bibliotecas se han cargado con dlopen: un controlador de mensajes al que se pueden suscribir los registradores y al que puede enviar mensajes. Requerido específicamente para los manejadores de señales.
El primer ejemplo no es seguro para subprocesos: si dos subprocesos llaman a getInstance al mismo tiempo, esa estática será una PITA. Alguna forma de mutex ayudaría.
El problema con los singletons no es su implementación. Es que combinan dos conceptos diferentes, ninguno de los cuales es obviamente deseable.
1) Singletons proporciona un mecanismo de acceso global a un objeto. Aunque podrían ser marginalmente más seguros para subprocesos o más confiables en idiomas sin un orden de inicialización bien definido, este uso sigue siendo el equivalente moral de una variable global. Es una variable global vestida con una sintaxis incómoda (foo :: get_instance () en lugar de g_foo, por ejemplo), pero cumple exactamente el mismo propósito (un solo objeto accesible en todo el programa) y tiene los mismos inconvenientes.
2) Los singletons previenen las múltiples instancias de una clase. Es raro, IME, que este tipo de característica se convierta en una clase. Normalmente es una cosa mucho más contextual; Muchas de las cosas que son consideradas como una y solo una son en realidad sólo sucede ser una sola. OMI, una solución más adecuada es crear una sola instancia, hasta que se dé cuenta de que necesita más de una instancia.
La mayoría de las personas usan singletons cuando intentan sentirse bien al usar una variable global. Existen usos legítimos, pero la mayoría de las veces cuando las personas los usan, el hecho de que solo pueda haber una instancia es solo un hecho trivial en comparación con el hecho de que es accesible a nivel mundial.
La verdadera desventaja de Singletons es que rompen la herencia. No puede derivar una nueva clase para darle funcionalidad extendida a menos que tenga acceso al código donde se hace referencia al Singleton. Por lo tanto, más allá del hecho de que el Singleton hará que su código esté bien acoplado (corregible por un Patrón de Estrategia ... también conocido como Dependency Injection), también evitará que cierre secciones del código de revisión (bibliotecas compartidas).
Por lo tanto, incluso los ejemplos de registradores o grupos de subprocesos no son válidos y deben reemplazarse por Estrategias.
Los Singletons son útiles cuando se ejecuta una gran cantidad de código al inicializar y objetar. Por ejemplo, cuando usa iBatis cuando configura un objeto de persistencia, tiene que leer todas las configuraciones, analizar los mapas, asegurarse de que todo está correcto, etc. antes de llegar al código.
Si hicieras esto cada vez, el rendimiento se degradaría mucho. Usándolo en un singleton, tomas ese golpe una vez y luego todas las llamadas subsiguientes no tienen que hacerlo.
Pero cuando necesito algo como un Singleton, a menudo termino usando un Contador Schwarz para crear una instancia.
Singletons te da la habilidad de combinar dos rasgos malos en una clase. Eso está mal en casi todos los sentidos.
Un singleton te da:
- Acceso global a un objeto, y
- Una garantía de que no se puede crear más de un objeto de este tipo
El número uno es sencillo. Los globales son generalmente malos. Nunca debemos hacer que los objetos sean accesibles globalmente a menos que realmente lo necesitemos.
El número dos puede parecer que tiene sentido, pero pensémoslo. ¿Cuándo fue la última vez que ** accidentalmente * creó un nuevo objeto en lugar de hacer referencia a uno existente? Como esto está etiquetado como C ++, usemos un ejemplo de ese lenguaje. ¿Usted escribe a menudo accidentalmente
std::ostream os;
os << "hello world/n";
Cuando pretendías escribir
std::cout << "hello world/n";
Por supuesto no. No necesitamos protección contra este error, porque ese tipo de error simplemente no ocurre. Si lo hace, la respuesta correcta es irse a casa y dormir entre 12 y 20 horas y esperar que se sienta mejor.
Si solo se necesita un objeto, simplemente cree una instancia. Si un objeto debe ser accesible globalmente, conviértalo en global. Pero eso no significa que deba ser imposible crear otras instancias de ello.
La restricción "solo una instancia es posible" realmente no nos protege contra posibles errores. Pero hace que nuestro código sea muy difícil de refactorizar y mantener. Porque muy a menudo descubrimos más tarde que sí necesitábamos más de una instancia. Tenemos más de una base de datos, tenemos más de un objeto de configuración, queremos varios registradores. Es posible que nuestras pruebas unitarias deseen poder crear y recrear estos objetos en cada prueba, para tomar un ejemplo común.
Por lo tanto, se debe usar un singleton si y solo si, necesitamos los dos rasgos que ofrece: si necesitamos acceso global (lo cual es raro, ya que los globales en general están desanimados) y debemos evitar que alguien cree más de una instancia de un clase (lo que me suena como un problema de diseño). La única razón por la que puedo ver esto es si crear dos instancias dañaría el estado de nuestra aplicación, probablemente porque la clase contiene una cantidad de miembros estáticos o tonterías similares. En cuyo caso la respuesta obvia es arreglar esa clase. No debería depender de ser la única instancia.
Si necesita acceso global a un objeto, conviértalo en un global, como std::cout
. Pero no restrinja el número de instancias que se pueden crear.
Si es absolutamente necesario, debe limitar la cantidad de instancias de una clase a solo una, y no hay forma de que la creación de una segunda instancia pueda manejarse de manera segura, y luego aplicarla. Pero no lo hagas accesible globalmente también.
Si necesita ambos rasgos, 1) conviértalo en un singleton, y 2) hágame saber para qué lo necesita, porque me cuesta mucho imaginar un caso así.
Todos ustedes están equivocados. Lea la pregunta. Responder:
Use un Singleton si:
- Necesita tener uno y solo un objeto de un tipo en el sistema
No use un Singleton si:
- Quieres ahorrar memoria
- Quieres probar algo nuevo
- Quieres mostrar cuánto sabes
- Porque todos los demás lo están haciendo (ver programador de cultos de carga en wikipedia)
- En los widgets de la interfaz de usuario.
- Se supone que es un caché
- En cuerdas
- En sesiones
- Puedo ir todo el dia
Cómo crear el mejor singleton:
- Cuanto más pequeño, mejor. Soy minimalista
- Asegúrese de que sea seguro para la rosca
- Asegúrate de que nunca sea nulo
- Asegúrese de que se crea una sola vez
- ¿Inicialización perezosa o del sistema? Hasta sus requerimientos
- A veces, el sistema operativo o la JVM crean singletons para usted (por ejemplo, en Java, cada definición de clase es un singleton)
- Proporcionar un destructor o de alguna manera averiguar cómo disponer los recursos
- Usa poca memoria
Una cosa con los patrones: no generalizar . Tienen todos los casos cuando son útiles, y cuando fallan.
Singleton puede ser desagradable cuando tienes que probar el código. Por lo general, se queda atascado con una instancia de la clase y puede elegir entre abrir una puerta en el constructor o algún método para restablecer el estado, etc.
Otro problema es que el Singleton de hecho no es más que una variable global disfrazada. Cuando tienes demasiado estado compartido global sobre tu programa, las cosas tienden a regresar, todos lo sabemos.
Puede hacer que el seguimiento de la dependencia sea más difícil. Cuando todo depende de su Singleton, es más difícil cambiarlo, dividirlo en dos, etc. Por lo general, se queda estancado. Esto también dificulta la flexibilidad. Investigue algún marco de inyección de dependencia para tratar de aliviar este problema.
Uso Singletons como prueba de entrevista.
Cuando le pido a un desarrollador que nombre algunos patrones de diseño, si todo lo que pueden nombrar es Singleton, no son contratados.
Modern C ++ Design by Alexandrescu tiene un singleton genérico heredable y seguro para subprocesos.
Para mi valor 2p, creo que es importante tener tiempos de vida definidos para sus singletons (cuando es absolutamente necesario usarlos). Normalmente no dejo que la función estática get()
ejemplifique nada, y dejo la configuración y la destrucción en una sección específica de la aplicación principal. Esto ayuda a resaltar las dependencias entre singletons, pero, como se subrayó anteriormente, es mejor evitarlas si es posible.
En las aplicaciones de escritorio (ya sé, ¡solo nosotros los dinosaurios escribimos esto!) Son esenciales para obtener una configuración global relativamente inmutable de la aplicación: el idioma del usuario, la ruta de acceso a los archivos de ayuda, las preferencias del usuario, etc. .
Edición: por supuesto, estos deben ser de solo lectura.
Otra implementacion
class Singleton
{
public:
static Singleton& Instance()
{
// lazy initialize
if (instance_ == NULL) instance_ = new Singleton();
return *instance_;
}
private:
Singleton() {};
static Singleton *instance_;
};
Todavía no entiendo por qué un singleton tiene que ser global.
Iba a producir un singleton donde escondía una base de datos dentro de la clase como una variable estática constante y privada y realizaba funciones de clase que utilizan la base de datos sin exponer la base de datos al usuario.
No veo por qué esta funcionalidad sería mala.
Los encuentro útiles cuando tengo una clase que encapsula mucha memoria. Por ejemplo, en un juego reciente en el que he estado trabajando, tengo una clase de mapa de influencia que contiene una colección de matrices muy grandes de memoria contigua. Quiero que todo se asigne al inicio, todo se libere al apagar y definitivamente quiero solo una copia. También tengo que acceder desde muchos lugares. Me parece que el patrón de singleton es muy útil en este caso.
Estoy seguro de que hay otras soluciones, pero me parece muy útil y fácil de implementar.
Si usted es el que creó el singleton y quien lo usa, no lo haga como singleton (no tiene sentido porque puede controlar la singularidad del objeto sin hacerlo singleton) pero tiene sentido cuando usted es un desarrollador de un biblioteca y desea proporcionar solo un objeto a sus usuarios (en este caso, usted es el que creó el singleton, pero usted no es el usuario).
Los Singletons son objetos, así que utilícelos como objetos, muchas personas acceden a singletons directamente a través de la llamada al método que lo devuelve, pero esto es perjudicial porque está haciendo que su código sepa que el objeto es Singleton. Prefiero usar Singletons como objetos. Los paso. A través del constructor y los uso como objetos ordinarios, de esa manera, su código no sabe si estos objetos son singletons o no, y eso hace que las dependencias sean más claras y ayuda un poco para refactorizar ...