Pregunta Patrón de diseño C ++ Singleton


Recientemente me he topado con una realización / implementación del patrón de diseño de 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. Lo que no está completamente claro para mí es cuándo exactamente se va a desasignar la memoria. ¿O hay un error y una fuga de memoria? Parece que hay un problema en la implementación.

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


545
2018-06-17 16:02


origen


Respuestas:


Consulte este artículo para obtener un diseño sencillo para un vago evaluado con Singleton de destrucción garantizada:
¿Alguien puede proporcionarme una muestra de Singleton en c ++? 

El singleton clásico evaluado y destruido correctamente.

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

Vea este artículo sobre cuándo usar un singleton: (no a menudo)
Singleton: ¿Cómo debería usarse?

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

Consulte este artículo que trata algunas implicaciones de subprocesos para singletons:
Instancia Singleton declarada como variable estática del método GetInstance

Consulte este artículo que explica por qué el bloqueo comprobado doble 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 controlado: parte I


841
2018-06-17 16:49



Al ser un Singleton, por lo general no desea que se destruya.

Se desmantelará 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 permite restaurarlo a un estado limpio, y hacer que se reasigne la próxima vez que se use, pero eso está fuera del alcance de una singleton "clásico".


36
2018-06-17 16:06



Podría evitar la asignación de memoria. Hay muchas variantes, todas tienen problemas en el caso del entorno de subprocesamiento múltiple.

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


27
2018-06-17 16:10



Otra alternativa no asignada: crear un singleton, por ejemplo, de clase C, como lo necesites:

singleton<C>()

utilizando

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

Ni esta ni la respuesta de Cătălin son automáticamente seguras para subprocesos en C ++ actual, pero estarán en C ++ 0x.


10
2018-06-17 16:15



Respuesta de @Loki Astari es excelente.

Sin embargo, hay momentos con múltiples objetos estáticos donde necesita poder garantizar que el semifallo no será destruido hasta que todos sus objetos estáticos que usan el semifallo ya no lo necesito

En este caso std::shared_ptr se puede usar para mantener el semifallo vivo para todos los usuarios, incluso cuando se invocan 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() {}
};

8
2017-10-31 06:56



Si desea asignar el objeto en el montón, ¿por qué no utiliza un puntero único? La memoria también será desasignada 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);

5
2018-01-11 21:11



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

Conocí este problema al intentar introducir un Singleton en la aplicación Qt. Decidí que todos los 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 la función "principal", 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 en el montón. Proporciono métodos explícitos de "init ()" y "term ()" 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 llamó "getInstance ()" o no.


4
2018-06-18 08:25



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 creado y esta referencia de objeto todas y cada una de las palabras posteriores.

SingletonClass instance created!
00915CB8
00915CB8

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

nótese bien Este no es un hilo de seguridad. Usted debe garantizar la seguridad del hilo.


4
2018-05-08 12:14



De hecho, es probable que esté asignado 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:

Singleton * Singleton::getInstance() {
    if (!instance) {
        instance = new Singleton();
    };
    return instance;
};

... y confía en que el programa salga del alcance para limpiarlo después.

Si trabaja en una plataforma donde la limpieza debe hacerse manualmente, probablemente agregue 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 aún no es demasiado importante si confía en la finalización del programa para limpiar de todos modos.


2
2018-06-17 16:14



Se trata de la gestión de objetos a lo largo de la vida. Supongamos que tiene más de un single en su software. Y ellos dependen del singleton de Logger. Durante la destrucción de la aplicación, supongamos que otro objeto singleton usa Logger para registrar sus pasos de destrucción. Debes garantizar que Logger debería ser limpiado al último. Por lo tanto, también echa un vistazo a este documento: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf 


1
2018-04-01 08:42



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>


1
2018-02-12 14:02