Расширяемый способ сделать информацию о классе доступной во время выполнения - PullRequest
4 голосов
/ 27 февраля 2012

Я делаю простую систему передачи сообщений.У меня есть таблица Entity Descriptor, подключенная к фабрике для создания подклассов Entity во время выполнения, и я хотел бы иметь ее, чтобы они могли быть созданы с помощью строки:

EntityManager manager; //managers have all of the entity table information (See below)

//Counter is a sample class that inherits from Entity
Counter* counter = manager.makeEntity("Counter"); //the string doesn't have to match the class name.

Теперь я знаю, что могу простоswitch switch, но я бы хотел, чтобы система также была расширяемой - то есть, когда я (или другие пользователи моей системы) хотим создать новый подкласс Entity, мне не нужно идти в блок switch и добавлятьЭто.В настоящее время я использую макросы для создания вспомогательных классов, которые я создаю статически, так что конструктор добавляет запись в таблицу сущностей.Эти классы также инициализируют сущности и удаляют множество шаблонов из конструкторов.

//EHandle is a wrapper for Entity*. Currently std::shared_ptr<Entity>

class GenericDesc
{
public:
virtual ~GenericDesc() {}
    virtual EHandle makeEntity() const =0;
};

namespace Descriptor
{
    //Adds a descriptor to an internal map<string, GenericDesc*>
    void addEntityDescriptor(const std::string& type, GenericDesc& desc);
    EHandle newEntity(const std::string& type); //Factory method
}

//Add this to every entity class definition
#define DECLARE_ENTITY_CLASS(CLASS_NAME) \
    friend class CLASS_NAME##Descriptor;


//Use these after a class definition to add the entity class to the descriptor table
#define BEGIN_ENTITY_TYPE(ENTITY_NAME, CLASS_NAME, BASE_NAME) \
    BEGIN_ENTITY_TYPE_GUTS(ENTITY_NAME, CLASS_NAME) \
        BASE_NAME##Descriptor::prepareEntity(ent);

#define BEGIN_ENTITY_TYPE_BASELESS(ENTITY_NAME, CLASS_NAME) \
    BEGIN_ENTITY_TYPE_GUTS(ENTITY_NAME, CLASS_NAME) \
        ent->self = ent;

#define BEGIN_ENTITY_TYPE_GUTS(ENTITY_NAME, CLASS_NAME) \
class CLASS_NAME##Descriptor : public GenericDesc \
{ \
private: \
    typedef CLASS_NAME ClassName; \
public: \
    CLASS_NAME##Descriptor() \
    { \
        Descriptor::addEntityDescriptor(ENTITY_NAME, *this); \
    } \
    virtual ~CLASS_NAME##Descriptor() {} \
    virtual EHandle makeEntity() const\
    { \
        auto ent = std::shared_ptr<CLASS_NAME>(new CLASS_NAME); \
        prepareEntity(ent); \
        ent->type = ENTITY_NAME; \
        return ent; \
    } \
    static void prepareEntity(std::shared_ptr<ClassName> ent) \
    {

//These functions are caled between BEGIN_ENTITY_TYPE and END_ENTITY_TYPE
//ADD_ENTITY_INPUT binds a function to a string
#define ADD_ENTITY_INPUT(INPUT_NAME, INPUT_FUNC) \
        ent->addInput(INPUT_NAME, std::bind(&ClassName::INPUT_FUNC, ent, std::placeholders::_1));
//ADD_ENTITY_OUTPUT binds an Output object to a string
#define ADD_ENTITY_OUTPUT(OUTPUT_NAME, OUTPUT_OBJECT) \
        ent->addOutput(OUTPUT_NAME, ent->OUTPUT_OBJECT);

#define END_ENTITY_TYPE(CLASS_NAME) \
    } \
}; \
static CLASS_NAME##Descriptor CLASS_NAME##Desc; //TODO: find a way to fix the multiple-static-allocation issue

Идея состоит в том, что вы создаете предложение BEGIN_ENTITY_TYPE (...) END_ENTITY_TYPE (...) с битами ADD_ENTITY_x всредний.Мой вопрос заключается в том, существует ли менее макро-y способ сделать это, который по-прежнему сводит к минимуму шаблон и не требует изменения каких-либо файлов, кроме того, который определяет подкласс Entity.Шаблонный класс мог бы работать, но я не знаю, как я мог бы делать вещи ADD_ENTITY_INPUT / OUTPUT с классом шаблона.

1 Ответ

0 голосов
/ 28 февраля 2012

Это может быть не совсем то, что вы ищете, но рассмотрите что-то вроде:

class Factory
{
public:
    virtual void InitialiseFactory() = 0;
    Entity* CreateEntity( string type )
    {
        // (obviously, you must handle the string not being found.)
        return m_MapEntities[ type ]->Clone();
    }
};

class MyFactory : public Factory
{
public:
    void InitialiseFactory()
    {
        m_MapEntities["typea"] = new MyEntity();
    } 
};

Идея здесь заключается в том, что поиск конкретизируется в базовом классе, который может остаться неизменным, но спецификакакие сущности предоставляются, находится в реализации производного класса.

Другой способ - статический метод в каждой сущности, выполняющий вызов фабрики, должен быть добавлен, но этот метод все равно необходимо вызывать откуда-то.

...