2016-06-10 16 views
9

Normalmente, se si conoscono tutti i tipi che si intende creare prima della mano, si può semplicemente fare qualcosa di simile:Come si può progettare una classe base, quindi conosce tutte le classi "derivate" in fase di esecuzione?

typedef enum{ 
    BASE_CREATURE_TYPE = 0, 
    ANIMAL_CREATURE_TYPE, 
    ... 
}CREATURE_TYPES 

Ma questo diventa noioso, perché ogni volta che si crea una nuova classe, è necessario aggiornare l'enum. Inoltre, CREATURE_TYPES è ancora solo elementi in un enum - come legare a una classe reale?

Mi chiedevo se ci fosse un modo, potevo semplicemente scrivere le classi, e in fase di esecuzione, senza istanziare realmente un oggetto, creare un set contenente tutti i tipi.

E 'possibile in C++? In Java c'è qualcosa chiamato "blocchi statici", che vengono eseguiti quando la classe viene caricata dalla JVM.

EDIT: Questa domanda non riguarda blocchi statici - È solo un esempio - Mi chiedo se c'è un modo, che posso eseguire un metodo o un blocco di codice in modo da sapere quali classi esistono in fase di esecuzione, senza effettivamente creazione di un oggetto

MODIFICA: Intendevo set di tutti i tipi, non "mappe", quindi posso creare un oggetto di ogni tipo, senza dover mantenere un elenco.

MODIFICA: Il motivo per cui lo voglio, è perché sto cercando di creare una funzione che possa chiamare metodi su tutte le classi derivate che fanno parte dell'applicazione. Ad esempio, dire che ho diverse classi che derivano tutte dalla classe Foo, e hanno uno sfere():

Foo{ 
    balls(); 
} 

Boo : public Foo{ 
    balls(); 
} 

Coo: public Foo{ 
    balls(): 
} 

In fase di esecuzione, mi piacerebbe sapere su tutte le classi derivate in modo da poter chiamare:

EDIT: Nota che non è necessario conoscere tutti i membri di ogni classe derivata, voglio solo sapere quali sono tutte le classi derivate, quindi posso chiamare balls(), su ognuna di esse.

EDIT: Questa domanda è simile: How to automatically register a class on creation

Ma, purtroppo, si sta memorizzando uno std :: string(). Come si fa riferimento alla classe attuale?

MODIFICA: Nella risposta di Smeehey di seguito, nel metodo principale, come creerei effettivamente un'istanza di ogni classe e chiamerei metodi sia statici che non statici?

+2

"Crea una mappa"? Che tipo di mappa? La mappa di Luke Skywalker? –

+0

Puoi spiegare perché lo vuoi? Probabilmente sarà più facile rispondere in questo modo. –

+0

@ShayNehmad Lo voglio in modo che possa popolare dinamicamente un menu, che è usato per creare i vari tipi derivati. Ma sarebbe molto noioso legare un pulsante a ogni tipo derivato, quindi stavo cercando di determinare se c'è un modo per "riflettere" tutte le classi derivate che fanno parte dell'applicazione così posso richiamare dinamicamente tutti i metodi, senza conoscendo ognuno dei tipi. –

risposta

4

È possibile creare un registro statico per tutte le classi e utilizzare un paio di macro di supporto per registrare nuovi tipi al suo interno. Di seguito è riportata una dimostrazione di lavoro di base, che crea 2 classi derivate da Base. Per aggiungere nuove classi devi semplicemente usare le due macro mostrate - una interna e una esterna alla classe. Nota: l'esempio è molto semplice e non si occupa di cose come il controllo di duplicati o altre condizioni di errore per massimizzare la chiarezza.

class BaseClass 
{ 
}; 

class Registry 
{ 
public: 
    static void registerClass(const std::string& name, BaseClass* prototype) 
    { 
     registry[name] = prototype;  
    } 

    static const std::map<std::string, BaseClass*>& getRegistry() { return registry; }; 

private: 
    static std::map<std::string, BaseClass*> registry; 
}; 

std::map<std::string, BaseClass*> Registry::registry; 

#define REGISTER_CLASS(ClassType) static int initProtoType() { static ClassType proto; Registry::registerClass(std::string(#ClassType), &proto); return 0; } static const int regToken; 
#define DEFINE_REG_CLASS(ClassType) const int ClassType::regToken = ClassType::initProtoType(); 

class Instance : public BaseClass 
{ 
    REGISTER_CLASS(Instance) 
}; 

DEFINE_REG_CLASS(Instance) 

class OtherInstance : public BaseClass 
{ 
    REGISTER_CLASS(OtherInstance) 
}; 

DEFINE_REG_CLASS(OtherInstance) 

int main() 
{ 
    for(auto entry : Registry::getRegistry()) 
    { 
     std::cout << entry.first << std::endl; 
    } 
    return 0; 
} 

I registri sopra prototipi delle classi derivate, che potrebbero essere utilizzati per copia-costruire altri casi, per esempio.In alternativa, richiesto dall'OP, è possibile avere un sistema in cui sono registrati i metodi di fabbrica anziché i prototipi. Ciò consente di creare istanze tramite un costruttore con ogni firma particolare, piuttosto che il costruttore di copia:

class BaseClass 
{ 
}; 

class Registry 
{ 
public: 
    using factoryMethod = BaseClass* (*)(int a, int b, int c); 

    static void registerClass(const std::string& name, factoryMethod meth) 
    { 
     registry[name] = meth;  
    } 

    static BaseClass* createInstance(const std::string& type, int a, int b, int c) 
    { 
     return registry[type](a, b, c); 
    } 

    static const std::map<std::string, factoryMethod>& getRegistry() { return registry; }; 

private: 
    static std::map<std::string, factoryMethod> registry; 
}; 

std::map<std::string, Registry::factoryMethod> Registry::registry; 

#define REGISTER_CLASS(ClassType) static BaseClass* createInstance(int a, int b, int c)  \ 
            {               \ 
             return new ClassType(a,b,c);       \ 
            }               \ 
            static int initRegistry()         \ 
            {               \ 
             Registry::registerClass(       \ 
              std::string(#ClassType),       \ 
              ClassType::createInstance);      \ 
             return 0;           \ 
            }               \ 
            static const int regToken;        \ 

#define DEFINE_REG_CLASS(ClassType) const int ClassType::regToken = ClassType::initRegistry(); 

class Instance : public BaseClass 
{ 
    Instance(int a, int b, int c){} 

    REGISTER_CLASS(Instance) 
}; 

DEFINE_REG_CLASS(Instance) 

class OtherInstance : public BaseClass 
{ 
    OtherInstance(int a, int b, int c){} 

    REGISTER_CLASS(OtherInstance) 
}; 

DEFINE_REG_CLASS(OtherInstance) 

int main() 
{ 
    std::vector<BaseClass*> objects; 
    for(auto entry : Registry::getRegistry()) 
    { 
     std::cout << entry.first << std::endl; 
     objects.push_back(Registry::createInstance(entry.first, 1, 2, 3)); 
    } 
    return 0; 
} 
+0

Sto ancora cercando di capovolgerlo: D. È molto da prendere per un noob –

+0

Questo è molto simile ai "blocchi statici" in Java a cui fai riferimento. Ci sono molte regole, ma fondamentalmente si riduce alla creazione di un'istanza statica di ogni classe e al recupero di questa istanza statica nella classe di registro. Al momento dell'esecuzione di 'main', tutte le istanze statiche saranno state inizializzate e registrate – Smeeheey

+0

È solo per VC++? –

1

utilizzare il disegno CRTP con interfaccia per "antenato" comune:

#include <vector> 
#include <iostream> 

/* Base */ 
struct IBase 
{ 
    virtual void balls() = 0; 
    virtual IBase *clone() const = 0; 

private: 
    static std::vector<IBase const *> _Derived; 

public: 
    static void 
    create_all(void) 
    { 
    std::cout << "size: " << _Derived.size() << "\n"; 
     for (IBase const *a : _Derived) 
     { 
      IBase *new_object(a->clone()); 
      (void)new_object; // do something with it 
     } 
    } 
}; 

std::vector<IBase const *> IBase::_Derived; 

/* Template for CRTP */ 
template<class DERIVED> 
class Base : public IBase 
{ 
    static bool  created; 
    static Base const *_model; 

public: 
    Base(void) 
    { 
     if (not created) 
     { 
      _Derived.push_back(this); 
      created = true; 
     } 
    } 
}; 

template<class DERIVED> 
bool Base<DERIVED>::created = false; 
template<class DERIVED> 
Base<DERIVED> const *Base<DERIVED>::_model = new DERIVED; 

/* Specialized classes */ 
struct Foo1 : public Base<Foo1> 
{ 
    IBase *clone() const 
    { 
     std::cout << "new Foo1\n"; 
     return new Foo1(*this); 
    } 
    void balls() {} 
}; 


struct Foo2 : public Base<Foo2> 
{ 
    IBase *clone() const 
    { 
     std::cout << "new Foo2\n"; 
     return new Foo2(*this); 
    } 
    void balls() {} 
}; 


int main(void) 
{ 
    Foo1 a; 
    IBase::create_all(); 
} 

ho provato questa soluzione, ma non so il motivo per cui il static Base const *_model; non si crea durante l'esecuzione del programma.

+2

Che sono più classi base. –

+0

@ DieterLücking con un'interfaccia, questo è buono – Boiethios

+0

CRTP sembra essere sulla strada giusta. Ma come conserverò una collezione di ogni classe derivata, quindi posso fare qualcosa come DerivedClass :: balls(); ? –

1

È possibile utilizzare una fabbrica globale funzioni in grado di creare oggetti (unique_ptr di) delle classi derivate tenuta:

#include <memory> 
#include <unordered_map> 
#include <typeinfo> 
#include <typeindex> 

// Factory 
// ======= 

template <typename Base> 
class Factory 
{ 
    public: 
    template <typename Derived> 
    struct Initializer { 
     Initializer() { 
      Factory::instance().register_producer<Derived>(); 
     } 
    }; 
    typedef std::function<std::unique_ptr<Base>()> producer_function; 
    typedef std::unordered_map<std::type_index, producer_function> producer_functions; 

    static Factory& instance(); 

    void register_producer(const std::type_info& type, producer_function producer) { 
     m_producers[std::type_index(type)] = std::move(producer); 
    } 

    template <typename Derived> 
    void register_producer() { 
     register_producer(
      typeid(Derived), 
      []() { return std::make_unique<Derived>(); }); 
    } 

    producer_function producer(const std::type_info& type) const { 
     auto kv = m_producers.find(std::type_index(type)); 
     if(kv != m_producers.end()) 
      return kv->second; 
     return producer_function(); 
    } 

    const producer_functions producers() const { return m_producers; } 

    private: 
    producer_functions m_producers; 
}; 

template <typename Base> 
Factory<Base>& Factory<Base>::instance() { 
    static Factory result; 
    return result; 
} 

// Test 
// ==== 

#include <iostream> 

class Base 
{ 
    public: 
    ~Base() {} 
    virtual void print() = 0; 
}; 

class A : public Base 
{ 
    public: 
    void print() override { std::cout << "A\n"; } 
    static void f() {} 
}; 
Factory<Base>::Initializer<A> A_initializer; 

class B : public Base 
{ 
    public: 
    void print() override { std::cout << "B\n"; } 
}; 
Factory<Base>::Initializer<B> B_initializer; 

class C {}; 

int main() 
{ 
    auto& factory = Factory<Base>::instance(); 

    // unique_ptr 
    auto producerA = factory.producer(typeid(A)); 
    if(producerA) { 
     auto ptrA = producerA(); 
     ptrA->print(); 
    } 

    // shared_ptr 
    auto producerB = factory.producer(typeid(B)); 
    if(producerB) { 
     std::shared_ptr<Base> ptrB(producerB()); 
     ptrB->print(); 
    } 

    // missing 
    auto producerC = factory.producer(typeid(C)); 
    if(! producerC) { 
     std::cout << "No producer for C\n"; 
    } 

    // unordered 
    for(const auto& kv : factory.producers()) { 
     kv.second()->print(); 
    } 
} 

Nota: la fabbrica non fornisce mezzi per chiamare funzioni membro statiche senza oggetto.

Problemi correlati