Sto cercando una factory astratta per i modelli di classe, in cui le classi si registrano automaticamente al momento dell'inizializzazione statica. Per le classi regolari (non basate su modelli), la soluzione è abbastanza semplice utilizzando membri statici. Ecco un esempio di soluzione (piuttosto semplicistica), che funziona bene:Registrazione automatica in fase di compilazione dei modelli di classe in 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 domanda che ho è come farlo funzionare quando la roba Vorrei iscrivermi/creare è più simile a:
template <typename T> class Base...
template <typename T> class D1 : public Base<T> ...
l'idea migliore che posso pensare è al modello fabbrica così, qualcosa di simile a:
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)
ma mi chiedo se c'è un modo migliore, o se qualcuno ha implementato un tale modello prima.
EDIT: rivisitando questo problema qualche anno dopo (e con i modelli variadici), posso avvicinarmi molto di più a quello che voglio semplicemente "registrando" le funzioni, o meglio le classi, come parametri del modello in fabbrica. Sarebbe simile a questa:
#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;
}
Ora, questo è solo un prototipo semplicistico, quindi non importa creare() 's complessità lineare. La mancanza principale di questo progetto, tuttavia, è che non consente alcun parametro del costruttore. Idealmente, sarei in grado di registrare non solo le classi che la factory ha bisogno di creare, ma anche i tipi che ogni classe prende nel suo costruttore, e lascia che create() li prenda in modo variabile. Qualcuno ha mai fatto qualcosa del genere prima?
Contrariamente alla credenza popolare, le funzioni * non * si registrano a * tempo di compilazione *, ma piuttosto all'avvio del programma, prima che venga chiamato 'main()'. Questo è in parte dovuto al fatto che 'registerFunc' non è dichiarato come' constexpr', ma anche perché non è realmente possibile il modo in cui lo hai scritto. –
Se vuoi essere elegante, puoi creare un singolo registro globale dei creatori di tipo 'std :: map'. Per recuperare il modello con il tipo 'T' devi cast' m [typeid (T)] 'a' Base (* create)() '. –
Ovviamente hai ragione sul tempo di compilazione/init. Dispiace per la confusione. Per quanto riguarda la tua idea di cancellazione del testo, non è male. Sarebbe stato bello se avessi potuto nascondere quei dettagli in un file di definizione, ma il modello T lo impedisce. – Eitan