2009-07-07 28 views
10

Recientemente recibí una pregunta sobre la implementación de Singleton, pero la clase base abstracta involucrada. Supongamos que tenemos jerarquía de clases como esto:Singleton y clase base abstracta en C++

class IFoo {...}; // it's ABC 
class Foo : public IFoo {...}; 

hemos definido la clase singleton de la siguiente manera:

template <typename T> 
class Singleton 
{ 
public: 
static T* Instance() { 
    if (m_instance == NULL) { 
     m_instance = new T(); 
    } 
    return m_instance; 
} 
private: 
static T* m_instance; 
}; 

Así que si quiero usar como siguiente: IFoo::Instance()->foo(); ¿qué debería hacer?

Si hago esto: class IFoo : public Singleton<IFoo> {...}; no funcionará ya que Singleton llamará al ctor de IFoo pero IFoo es un ABC, por lo que no se puede crear.

Y esto: class Foo : public IFoo, public Singleton<Foo> {...}; no puede funcionar también, porque de esta forma la clase IFoo no tiene la interfaz para el método Instance(), por lo que la llamada IFoo::Instance() fallará.

¿Alguna idea?

+0

puede usted por favor formatear el código – RvdK

+0

hecho, lo siento por eso –

+3

por favor no utilice este patrón singelton. Es el clásico anti-patrón para la implementación de singelton, ya que es difícil destruir correctamente la instancia. http://stackoverflow.com/questions/86582/singleton-how-should-it-be-used –

Respuesta

8

No puede hacer esto. IFoo es una interfaz, por diseño y definición. Por lo tanto, el número de instancias es 0. Por otro lado, la definición de una clase singleton es que tiene 1 instancia. 0 = 1.

10

te gustaría usar algo como

IFoo my_foo = Singleton<Foo>::Instance(); 
my_foo->foo(); 

Básicamente, usted tendrá que crear una instancia de la plantilla Singleton utilizando una clase concreta (en este caso, la clase Foo) y dado que su Foo deriva de IFoo, puede referirse a él a través de un puntero de base. No puede instanciar directamente una plantilla utilizando una clase incompleta o abstracta.

2

siempre se puede hacer algo como esto: "¿por qué está usando un producto único"

class IFoo {}; 
class Foo : public IFoo {}; 

template <typename T> 
class Singleton 
{ 
    // .. 
}; 

typedef Singleton<Foo> FooSingleton; 

int main() 
{ 
    FooSingleton::Instance()->foo(); 

    return 0; 
} 
2

El molesto meta-respuesta es: Todavía tengo que encontrar una situación en la que realmente necesite usarlo. En mi humilde opinión, sus inconvenientes superan sus ventajas, en situaciones de la vida real que es.

Usar algo como 'boost :: noncopyable' podría ser lo que estás buscando.

See this post for more info

+2

Estoy de acuerdo con su punto. Pero aún necesita aprender cómo hacerlo (y hacerlo correctamente) solo para que sepa los problemas que agrega (acoplamiento ajustado). –

0

Míralo de esta manera: No hay nada en el programa que decirle al compilador qué interfaz de la aplicación IFoo cabe instanciando. Recuerde, podría haber otras implementaciones además de Foo.

Si desea utilizar una clase a través de una interfaz y definir qué implementación real se debe usar en otro lugar, consulte el patrón Abstract Factory.

0

Tuve que hacer algo similar para agregar pruebas unitarias a algún código heredado. Tuve que reemplazar un singleton existente que usaba una plantilla. Di dos parámetros a la plantilla de singleton, el primero es la interfaz, el segundo es la implementación.

Sin embargo, también tuve que agregar un método setTestInstance para permitir que las pruebas de unidad anularan la instancia en tiempo de ejecución.

template <typename IfaceT, typename ImplT> 
class Singleton 
{ 
public: 
    static IfaceT* Instance() { 
     if (m_instance == NULL) { 
     m_instance = new ImplT(); 
     } 
     return m_instance; 
    } 

    // Only used for unit tests 
    // Takes ownership of instance 
    static void setTestInstance(IfaceT* instace) { 
     m_instance = instance; 
    } 
private: 
    static IfaceT * m_instance; 
}; 

En este caso setTestInstance debe utilizar un std::auto_ptr y m_instance debería haber un boost::scoped_ptr. Para evitar pérdidas de memoria.

0

Creo que la mejor solución sería introducir una clase o método de fábrica aquí. Imagínese lo siguiente:

struct FooCreator 
{ 
    typedef IFoo*  result_type; 

    result_type operator()()const 
    { 
    return new Foo; 
    } 
}; 

template<class Factory> 
struct Singleton 
{ 

    static typename Factory::result_type instance() 
    { 
    if(instance_==typename Factory::result_type()) 
     instance_ = Factory()(); 
    return instance_; 
    } 

private: 
    Singleton(){}; 

    static typename Factory::result_type instance_; 
}; 

template<class F> 
typename F::result_type Singleton<F>::instance_ = typename F::result_type(); 

Best Regards,
Ovanes

1

Aquí hay otra solución posible encontré que funciona muy bien.

añadir esto a Singleton:

#ifndef ABSTRACT_CLASS 
    static T* D() 
    { 
     return new T(); 
    } 
#else 
    static T* D() 
    { 
     return NULL; 
    } 
#endif 

static T* Instance(T*(*func)()) 
{ 
    if(!m_instance) 
    { 
     m_instance = func(); 
    } 

    return m_instance; 
} 

static T* Instance() 
{ 
    if(!m_instance) 
    { 
     m_instance = D(); 
    } 

    return m_instance; 
} 

Asegurar la clase abstracta es en un encabezado, mientras que las implementaciones se encuentran en las fuentes.

Por ejemplo:

// IFoo.h 
// 
#define ABSTRACT_CLASS 

class IFoo 
{ 
    virtual ~IFoo() {} 

    virtual void SomeFunc() = 0; 
}; 

extern IFoo* BuildFoo(); 


// Foo.cpp 
// 
#include "IFoo.h" 

class Foo : public IFoo 
{ 
    Foo() {} 
    ~Foo() {} 

    void SomeFunc() {} 
}; 

IFoo* BuildFoo() { return new Foo(); } 

Con estas adiciones, ahora se puede hacer lo siguiente:

IFoo::Instance(BuildFoo); 

IFoo::Instance()->SomeFunc(); 

Sólo recuerde a #define ABSTRACT_CLASS en la cabecera para cada clase abstracta.

0

Me he encontrado con el mismo problema recientemente.

Se puede implementar con lo que conozco como gem singleton. Se utiliza para forzar assert singularidad y Curiously recurring template pattern para llamar implementación de la interfaz a través de Singleton:

template <typename T> 
class Singleton { 
public: 
    Singleton(const Singleton<T>&) = delete; 
    Singleton& operator=(const Singleton<T>&) = delete;  
    Singleton() { 
    assert(!msSingleton); 
    msSingleton = static_cast<T*>(this); 
    } 
    ~Singleton(void) { 
    assert(msSingleton); 
    msSingleton = 0; 
    } 
    static T& getSingleton(void) { 
    assert(msSingleton); 
    return (*msSingleton); 
    } 
protected: 
    static T* msSingleton; 
};  

class IFoo : public Singleton<IFoo> {  
public: 
    virtual void foo() = 0; 
}; 

class FooImpl : public IFoo { 
public: 
    FooImpl(); 
    void foo() override { std::cout << "FooImpl::foo()\n"; } 
}; 

template <> 
IFoo* Singleton<IFoo>::msSingleton = 0; 

FooImpl::FooImpl() { msSingleton = this; } 

Tras crear la instancia manualmente FooImpl, llamado de IFoo::getSingleton().foo() llamará código FooImpl 's.

int main() { 
    FooImpl f; 
    IFoo::getSingleton().foo(); 
} 

demo