I'm looking for an abstract factory for class templates, where the classes register themselves automatically at static initialization time. For regular (non-templated) classes, the solution is straightforward enough using static members. Here's an example of a (rather simplistic) solution that works just fine:
#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;
}
The question I have is how to make it work when the stuff I want to register/create is more like:
template <typename T> class Base...
template <typename T> class D1 : public Base<T> ...
The best idea I can think of is to template the factory as well, something like:
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)
But I'm wondering if there's a better way, or if someone has implemented such a pattern before.
EDIT: revisiting this problem a few years later (and with variadic templates), I can get much closer to what I want by simply "registering" functions, or rather classes, as template parameters to the factory. It would look something like this:
#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;
}
Now, this is just a simplistic prototype, so never mind create()'s linear complexity. The main deficiency of this design, however, is that it doesn't allow for any constructor parameters. Ideally, I'd be able to register not only the classes the factory needs to create, but also the types each class takes in its constructor, and let create() take them variadically. Has anyone ever done something like this before?
main()
is called. This is in part becauseregisterFunc
is not declared asconstexpr
, but also because it isn't really possible the way you wrote it. – Clausstd::map<std::type_info, void(*)()>
. To retrieve templated by typeT
you'd castm[typeid(T)]
toBase<T>(*create)()
. – Claus