2011-10-06 6 views
8

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?

+4

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. –

+1

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

+0

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

risposta

0

Fare cose più semplici si spezzerà di meno e rende evidenti le tue intenzioni.

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

Quando queste funzioni init sono effettivamente chiamati (non in fase di compilazione) e non riescono non sarà possibile ottenere tutte le cose abbastanza facili che rende più facile il debug. Come una traccia dello stack.

Con questo scenario si assume anche che non si desidera inizializzarli in modo diverso. Ad esempio, è estremamente complicato eseguire un test dell'unità "registrato automaticamente".

Meno magia = manutenzione più semplice e più economica.

Se ciò non bastasse, ci sono anche problemi tecnici. Ai compilatori piace togliere i simboli inutilizzati dalle biblioteche. Potrebbe esserci uno shenanigan specifico del compilatore per aggirarlo, non ne sono sicuro. Speriamo che lo faccia in modo coerente, invece che a caso per nessuna ragione ovvia nel bel mezzo di un ciclo di sviluppo.

+0

Sono d'accordo con il principio generale che meno magia è più facile. Apprezzo che tu stia cercando di indirizzarmi a fare la "Cosa Giusta" generale. Per favore, fidati del fatto che sono ben informato sulla soluzione semplice e ho intenzionalmente e specificamente chiesto questo caso d'angolo. Il mio uso effettivo è più complesso rispetto all'esempio semplicistico che ho dimostrato, perché non voglio confondere la domanda con dettagli che non sono rilevanti per la specifica sfida tecnica. – Eitan

+0

La breve storia sul motivo per cui insisto sulla registrazione automatica è la scalabilità e l'estensibilità: ho in programma di avere molti oggetti nella gerarchia e non voglio dimenticare di registrarli manualmente. Inoltre, la libreria è progettata per consentire a chiunque di aggiungere i propri oggetti derivati ​​senza dover toccare un punto centrale di registrazione. Argomentarlo ulteriormente richiederebbe più contesto - ma potremmo concentrarci invece sulla mia domanda iniziale? – Eitan

+2

@Tom Questo in realtà sarebbe piuttosto difficile da mantenere in progetti più grandi. Ogni volta che aggiungi o rimuovi una classe devi aggiungere un'altra chiamata di funzione in un file completamente diverso e magari persino creare una funzione di registro diversa per ogni tipo. –

3

Ho postato una risposta a un problema simile su GameDev, ma la soluzione non è tempo di compilazione. Puoi verificarlo qui:
>https://gamedev.stackexchange.com/questions/17746/entity-component-systems-in-c-how-do-i-discover-types-and-construct-components/17759#17759

Io non credo che ci sia anche un modo per rendere questo momento della compilazione. Il tuo "id" all'interno della classe base è in realtà solo una forma semplificata di RTTI, che è per definizione run-time. Forse se hai reso l'id un argomento modello ... ma questo renderebbe alcune altre cose molto più complicate.

+0

Hai ragione riguardo al tempo di compilazione, ovviamente. Ho modificato la domanda originale per eliminare la confusione. – Eitan

+0

Correggimi se ho torto, ma il tuo post è semplicemente un modo più semplice per implementare l'esempio originale che ho postato sopra. Non sembra che risolva il problema del class class factory. – Eitan

+0

Hmm, hai ragione. Se vuoi mantenere solo un factory, l'unica opzione è avere una classe base non-template. Forse sarebbe una buona idea postare uno schizzo di come sarebbe l'interfaccia ideale per te. :) –