2011-10-06 8 views
8

Estoy buscando una fábrica abstracta para plantillas de clase, donde las clases se registran automáticamente en el momento de la inicialización estática. Para las clases regulares (sin plantilla), la solución es lo suficientemente simple usando miembros estáticos. He aquí un ejemplo de una solución (más bien simplista) que funciona bien:Registro de fábrica automático de plantillas de clase en C++

#include <cassert> 
#include <iostream> 

class Base { 
public: 
    virtual size_t id() const = 0; 
    virtual const char* name() const = 0; 
    virtual ~Base() {} 
}; 

typedef Base* (*CreateFunc)(void); 

class SimpleFactory { 
private: 
    static const size_t NELEM = 2; 
    static size_t id_; 
    static CreateFunc creators_[NELEM]; 

public: 
    static size_t registerFunc(CreateFunc creator) { 
    assert(id_ < NELEM); 
    assert(creator); 
    creators_[id_] = creator; 
    return id_++; 
    } 

    static Base* create(size_t id) { assert(id < NELEM); return (creators_[id])(); } 
}; 

size_t SimpleFactory::id_ = 0; 
CreateFunc SimpleFactory::creators_[NELEM]; 


class D1 : public Base { 
private: 
    static Base* create() { return new D1; } 
    static const size_t id_; 

public: 
    size_t id() const { return id_; } 
    const char* name() const { return "D1"; } 
}; 

const size_t D1::id_ = SimpleFactory::registerFunc(&create); 

class D2 : public Base { 
private: 
    static Base* create() { return new D2; } 
    static const size_t id_; 

public: 
    size_t id() const { return id_; } 
    const char* name() const { return "D2"; } 
}; 

const size_t D2::id_ = SimpleFactory::registerFunc(&create); 

int main() { 
    Base* b1 = SimpleFactory::create(0); 
    Base* b2 = SimpleFactory::create(1); 
    std::cout << "b1 name: " << b1->name() << "\tid: " << b1->id() << "\n"; 
    std::cout << "b2 name: " << b2->name() << "\tid: " << b2->id() << "\n"; 
    delete b1; 
    delete b2; 
    return 0; 
} 

La pregunta que tengo es cómo hacer que funcione cuando la materia que desea registrar/Crear es más como:

template <typename T> class Base... 
template <typename T> class D1 : public Base<T> ... 

la mejor idea que se me ocurre es a la plantilla de la fábrica, así, algo así como:

template <typename T> 
class SimpleFactory { 
private: 
    static const size_t NELEM = 2; 
    static size_t id_; 
    typedef Base<T>* Creator; 
    static Creator creators_[NELEM]; 
...(the rest remains largely the same) 

pero me pregunto si hay una mejor manera, o si alguien ha puesto en marcha un patrón semejante.

EDIT: revisando este problema unos años más tarde (y con plantillas variadic), puedo acercarme mucho más a lo que quiero simplemente registrando funciones, o más bien clases, como parámetros de plantilla de fábrica. Se vería algo como esto:

#include <cassert> 

struct Base {}; 

struct A : public Base { 
    A() { std::cout << "A" << std::endl; } 
}; 

struct B : public Base { 
    B() { std::cout << "B" << std::endl; } 
}; 

struct C : public Base { 
    C() { std::cout << "C" << std::endl; } 
}; 

struct D : public Base { 
    D() { std::cout << "D" << std::endl; } 
}; 


namespace { 
    template <class Head> 
    std::unique_ptr<Base> 
    createAux(unsigned id) 
    { 
    assert(id == 0); 
    return std::make_unique<Head>(); 
    } 

    template <class Head, class Second, class... Tail> 
    std::unique_ptr<Base> 
    createAux(unsigned id) 
    { 
    if (id == 0) { 
     return std::make_unique<Head>(); 
    } else { 
     return createAux<Second, Tail...>(id - 1); 
    } 
    } 
} 

template <class... Types> 
class LetterFactory { 
public: 
    std::unique_ptr<Base> 
    create(unsigned id) const 
    { 
    static_assert(sizeof...(Types) > 0, "Need at least one type for factory"); 
    assert(id < sizeof...(Types)); 
    return createAux<Types...>(id); 
    } 
}; 

int main() { 
    LetterFactory<A, B, C, D> fac; 
    fac.create(3); 
    return 0; 
} 

Ahora, esto es sólo un prototipo simple, por lo que no importa crear() 's complejidad lineal. La principal deficiencia de este diseño, sin embargo, es que no permite ningún parámetro de constructor. Idealmente, podría registrar no solo las clases que la fábrica necesita para crear, sino también los tipos que cada clase toma en su constructor, y dejar que create() los tome de forma variada. ¿Alguien ha hecho algo como esto antes?

+4

Contrariamente a la creencia popular, sus funciones no * se * registran en el * tiempo de compilación *, sino más bien al inicio del programa, antes de que se llame a 'main()'. Esto se debe en parte a que 'registerFunc' no está declarado como' constexpr', sino también porque no es posible en la forma en que lo escribiste. –

+1

Si quieres ser elegante, puedes crear un único registro de creador global de tipo 'std :: map '. Para recuperar templated por tipo 'T', se convertiría' m [typeid (T)] 'a' Base (* create)() '. –

+0

Por supuesto, tiene razón sobre el tiempo de compilación/tiempo de inicio. Perdón por la confusion. En cuanto a su idea de borrado de tipo, no está mal. Hubiera sido bonito si hubiera podido ocultar esos detalles en un archivo de definición, pero la plantilla T lo impide. – Eitan

Respuesta

0

Hacer cosas más simples se romperá menos y hará que sus intenciones sean obvias.

int main() { 
    RegisterConcreteTypeFoo(); 
    RegisterConcreteTypeBar(); 
    // do stuff... 
    CleanupFactories(); 
    return 0; 
} 

Cuando esas funciones init se llama en realidad (no en tiempo de compilación) y fallan no obtendrá todas las cosas bastante fáciles que hace más fácil la depuración. Como un rastro de pila.

Con este escenario, también asume que no desea inicializarlos de manera diferente. Es excesivamente complicado para la prueba unitaria "registrar automáticamente" cualquier cosa, por ejemplo.

Menos magia = mantenimiento más fácil y económico.

Si eso no fuera suficiente, también hay problemas técnicos. A los compiladores les gusta quitar los símbolos no utilizados de las bibliotecas. Puede haber un shenanigan específico del compilador para evitarlo, no estoy seguro. Esperemos que lo haga de manera consistente, en lugar de aleatoriamente sin una razón obvia en el medio de un ciclo de desarrollo.

+0

Estoy de acuerdo con el principio general de que menos magia es más fácil. Aprecio que trates de dirigirme para hacer el "correcto" general. Por favor, confíen en que estoy bien informado sobre la solución simple, y he solicitado este caso de manera intencional y específica. Mi uso real es más complejo que el ejemplo simplista que demostré, porque no quiero confundir la pregunta con detalles que no son relevantes para el desafío técnico específico. – Eitan

+0

La breve historia sobre por qué insisto en el registro automático es escalabilidad y extensibilidad: planeo tener muchos objetos en la jerarquía, y no quiero olvidar registrarlos manualmente. Además, la biblioteca está diseñada para que cualquiera pueda agregar sus propios objetos derivados sin tener que tocar un punto central de registro. Discutir esto más a fondo requeriría más contexto, ¿pero podemos enfocarnos en mi pregunta original? – Eitan

+2

@Tom Esto realmente sería bastante difícil de mantener en proyectos más grandes. Cada vez que agregue o elimine una clase, también deberá agregar una llamada de función más en un archivo completamente diferente e incluso crear una función de registro diferente para cada tipo. –

3

He publicado una respuesta a un problema similar en GameDev, pero la solución no es el tiempo de compilación. Puede verificarlo aquí:
>https://gamedev.stackexchange.com/questions/17746/entity-component-systems-in-c-how-do-i-discover-types-and-construct-components/17759#17759

No creo que haya siquiera una forma de hacer este tiempo de compilación. Su "id" dentro de la clase base es realmente una forma simplificada de RTTI, que es, por definición, tiempo de ejecución. Tal vez si convirtieras el ID en un argumento de plantilla ... pero eso haría que otras cosas fueran mucho más complicadas.

+0

Tienes razón sobre el tiempo de compilación, por supuesto. Edité la pregunta original para aclarar la confusión. – Eitan

+0

Corrígeme si me equivoco, pero su publicación es simplemente una manera más clara de implementar el ejemplo original que publiqué anteriormente. No parece resolver el problema de fábrica de la clase de plantilla. – Eitan

+0

Hmm, tienes razón. Si desea mantener solo una fábrica, la única opción es tener una clase base que no sea de plantilla. Tal vez sería una buena idea publicar un boceto de cómo sería una interfaz ideal para ti. :) –

Cuestiones relacionadas