2010-12-05 11 views
9

Estoy tratando de crear una plantilla de fábrica abstracta para varias fábricas abstractas en C++ y se me ocurrió esto.C++ Abstract Factory usando plantillas

#define _CRTDBG_MAP_ALLOC 
#include <crtdbg.h> 
#include <map> 
#include <stdio.h> 

class Base 
{ 
public: 
    virtual ~Base() {} 

    virtual bool Get() = 0; 
}; 

class DerivedA : public Base 
{ 
public: 
    bool Get() 
    { 
     return true; 
    } 
}; 

class DerivedB : public Base 
{ 
public: 
    bool Get() 
    { 
     return false; 
    } 
}; 

template <class T> 
class Creator 
{ 
public: 
    virtual ~Creator(){} 
    virtual T* Create() = 0; 
}; 

template <class T> 
class DerivedCreator : public Creator<T> 
{ 
public: 
    T* Create() 
    { 
     return new T; 
    } 
}; 

template <class T, class Key> 
class Factory 
{ 
public: 
    void Register(Key Id, Creator<T>* Fn) 
    { 
     FunctionMap[Id] = Fn; 
    } 

    T* Create(Key Id) 
    { 
     return FunctionMap[Id]->Create(); 
    } 

    ~Factory() 
    { 
     std::map<Key, Creator<T>*>::iterator i = FunctionMap.begin(); 
     while (i != FunctionMap.end()) 
     { 
      delete (*i).second; 
      ++i; 
     } 
    } 
private: 
    std::map<Key, Creator<T>*> FunctionMap; 
}; 

int main(int argc, char** argv[]) 
{ 
    _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF); 

    //Register 
    Factory<Base, char*> temp; 
    temp.Register("DA", (Creator<Base>*)new DerivedCreator<DerivedA>); 
    temp.Register("DB", (Creator<Base>*)new DerivedCreator<DerivedB>); 

    //Pointer to base interface 
    Base* pBase = 0; 

    //Create and call 
    pBase = temp.Create("DA"); 
    printf("DerivedA %u\n", pBase->Get()); 
    delete pBase; 

    //Create and call 
    pBase = temp.Create("DB"); 
    printf("DerivedB %u\n", pBase->Get()); 
    delete pBase; 

return 0; 
} 

Se compila y funciona muy bien sin pérdidas de memoria (win32 crtdbg), pero no sé si esto es realmente la forma correcta de hacer una plantilla de la fábrica abstracta.

temp.Register("DA", (Creator<Base>*)new DerivedCreator<DerivedA>); 

También me pregunto acerca de la línea anterior. Estoy confundido por qué tengo que lanzar. No entiendo las plantillas muy bien, pero supongo que debería funcionar bien teniendo en cuenta que tanto la clase de plantilla como la clase real se derivan.

Ese código realmente funciona bien como se muestra arriba e incluso elimina bien sin pérdidas de memoria. Simplemente no me siento del todo cómodo con eso.

no he podido encontrar ejemplos reales de clases de plantilla a excepción de esto desde mangos (WOW emulador) - https://mangos.svn.sourceforge.net/svnroot/mangos/trunk/src/framework/Dynamic/ObjectRegistry.h

Pero no creo que pueda usar ese método en mi proyecto porque pienso sobre el uso de archivos DLL en algún momento de mi proyecto y utiliza CRTP que va en contra de mi requerimiento de polimorfismo en tiempo de ejecución.

+0

Sí, la línea informados es malo. No hay relación entre los dos tipos. Están especializados para diferentes tipos. Tampoco estoy seguro de por qué te molestas con CRTP en absoluto. Por lo general, se usa para * evitar * funciones virtuales. Pero todavía tienes esos, ¿por qué molestarse con las plantillas? – jalf

+0

Bueno, lo que intento hacer es crear una solución de 3 partes. Programa, biblioteca y DLL. La DLL contendrá la implementación, The Library contiene la fábrica y el programa usa la interfaz. La plantilla existe porque voy a estar haciendo esto mucho. Lo estoy usando para reemplazar la selección de controladores de mi motor de juego actual. Actualmente tiene un código de copia/pegado para video, física, entrada y audio. – NtscCobalt

Respuesta

10

La clase DerivedCreator<DerivedA> no es un Creator<DerivedA> un Creator<Base>.

tiene que decirle a la plantilla derivada lo que el tipo base para que pueda implementar la interfaz de Creator<Base> mediante la creación de una instancia del tipo derivado:

// DerivedCreator is Creator<BaseType> which creates a 
// DerivedType, not a Creator<DerivedType> 
template <class DerivedType, class BaseType> 
class DerivedCreator : public Creator<BaseType> 
{ 
public: 
    BaseType* Create() 
    { 
     return new DerivedType; 
    } 
}; 

// Register 
Factory<Base, std::string> temp; 
temp.Register("DA", new DerivedCreator<DerivedA, Base>); 
temp.Register("DB", new DerivedCreator<DerivedB, Base>); 

// or if you want to create lots with the same base: 
template <class DerivedType> 
class DerivedBaseCreator : public DerivedCreator<DerivedType, Base> {}; 

//Register 
Factory<Base, std::string> temp; 
temp.Register("DA", new DerivedBaseCreator<DerivedA>); 
temp.Register("DB", new DerivedBaseCreator<DerivedB>); 
+0

Ah gracias, creo que esto es exactamente lo que estaba buscando. Por mi cuenta no entendí que "La clase DerivedCreator es un Creador que no es un Creador ". – NtscCobalt

0

pequeñas observaciones para mejorar el diseño: 1) Uso shared_ptr en lugar de punteros primas 2) utilizar std :: string en lugar de char *

tienen que desechar, porque los tipos Creador, Creador y Creador < DerivedB > son tipos completamente diferentes. La manera de arreglar eso es quitar los moldes:

//Register 
Factory<Base, char*> temp; 
temp.Register("DA", new DerivedCreator<Base>); 
temp.Register("DB", new DerivedCreator<Base>); 
+2

Sustituir ciegamente los punteros crudos por 'shared_ptr' es una muy mala idea. No solo te arriesgas a obtener referencias circulares, y las fugas de memoria asociadas, sino que también es el tipo de puntero inteligente más lento. Usa punteros inteligentes, sí. Pero solo usa 'shared_ptr' específicamente cuando necesites propiedad compartida. – jalf

+0

Sí, definitivamente estoy de acuerdo en que alejarse de Char * es una buena idea. Encontré uno de la manera difícil cuando llamé al pBase = temp.Create ("DA"); desde un espacio de direcciones separado de la función de registro. Planeo usar punteros inteligentes pero soy mejor en la programación simple sin ellos, así que los agregaré más adelante. – NtscCobalt

-1

Puede hacer Fábrica :: El registro de un método de la plantilla y de impulsar el uso de mpl valer dentro

#include <boost/mpl/assert.hpp> 
#include <boost/type_traits.hpp> 

template <class T, class Key> 
class Factory 
{ 
public: 
/////////////////////////////////// 
template <typename _Base> 
void Register(Key Id, Creator<_Base> * Fn) 
{ 
    BOOST_MPL_ASSERT((boost::is_base_of<T, _Base>)); 

    FunctionMap[Id] = reinterpret_cast<Creator<T>*>(Fn); 
} 
/////////////////////////////////// 
//... 
}; 
+0

Lo único que puedes hacer con 'reinterpret_cast' es devolver el resultado al tipo original, por lo que para usar' FunctionMap' deberías conocer los tipos específicos del creador que obtienes de él, lo cual no es una opción viable . –

+0

No tiene que devolverlo porque assert garantiza que _Base se deriva de (o es) tipo T, el compilador se asegura de que Fn apunte al tipo Creador <"whatever">. – TomK