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?
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. –
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)() '. –
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