Функция вызова из производного класса, хранящаяся на карте с типом базового класса

Я пытаюсь создать игровой движок, используя Gameobject и компоненты, похожие на Unity. У меня есть класс компонентов:

class Component
{
public:
    virtual void DoTheThing(){//Doing the thing};
}

Класс текстуры, производный от компонента, с переопределенной функцией:

class Texture : public Component
{
public:
    void DoTheThing(){//Doing a different thing};
}

Класс GameObject с картой компонентов, в которой хранятся производные компоненты:

class GameObject
{
private:
    map<string, Component> components;
    Texture texture;
    components["id"] = texture;

public:    
    Component getComponent(string id){ return components[id];}
}

И наконец:

int main(int argc, char* args[])
{
    GameObject gameObject;
    gameObject.getComponent("id").DoTheThing();
}

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


person Tanukibo    schedule 03.12.2016    source источник
comment
Вы скорее хотите что-то вроде map<string, std::shared_ptr<Component>> components; ??   -  person πάντα ῥεῖ    schedule 04.12.2016
comment
Спасибо за вашу помощь, люди :)   -  person Tanukibo    schedule 04.12.2016


Ответы (2)


Ваш контейнер содержит Component объекты, поэтому, когда вы выполняете components["id"] = texture;, вы фактически помещаете Component объект на карту, а конструктор копирования вызывается для объекта texture.

Чтобы обеспечить полиморфизм, ваш контейнер должен содержать указатели, а не предопределенный класс.

class Component
{  public:  virtual void DoTheThing() {cout << "Component\n";} };
class Texture : public Component
{  public:  void DoTheThing() override { cout << "Texture\n"; } };

class GameObject
{   map<string, Component*> components; Texture texture;
    public:
        GameObject() { components["id"] = &texture; }
        Component& getComponent(string id) { return *components[id]; }
        // Remember to return a Component& (or Component*) here, not a
        // Component (by value) because otherwise you would end up with a 
        // base-class Component object copied from the initial Texture
        // object saved in the map.
};

int main(void)
{   GameObject gameObject;
    gameObject.getComponent("id").DoTheThing();
    return 0;
}

РЕЗУЛЬТАТ: Текстура

person A.S.H    schedule 03.12.2016
comment
Исходная проблема известна как нарезка объектов. Использование указателей позволяет избежать нарезки. И кстати, Component& getComponent() вылетит, если id еще не находится в map, так как components[id] вернет указатель NULL, который не может быть разыменован. Если вы собираетесь вернуть ссылку Component&, вы должны использовать map::find() вместо map::operator[] и создать собственное исключение, если find() ничего не находит. В противном случае getComponent() вместо этого должен вернуть указатель Component* (который вызывающая сторона должна будет проверить перед использованием). - person Remy Lebeau; 04.12.2016
comment
@RemyLebeau ваши правильные наблюдения могут сделать код лучше и безопаснее. Однако ответ сосредоточен на основной проблеме вопроса, и, надеюсь, идея была уловлена, и ОП приобретет такую ​​​​хорошую практику с опытом. - person A.S.H; 04.12.2016

Проблема в вашем GameObject классе.
Позвольте мне сократить ее до соответствующих частей:

class GameObject {
private:
    map<string, Component> components;

    // ...

public:
    Component getComponent(string id){ /* do something and return a component */ }
};

Здесь вы фактически нарезаете свои объекты.
Вот почему вызывается метод из базового класса.


Если вы не знаете, что такое нарезка и как ее избежать, я бы не рекомендовал вам писать что-то похожее на Unity.
Я бы посоветовал вам прочитать этот вопрос/ответ для получения дополнительной информации.

person skypjack    schedule 03.12.2016
comment
можно поподробнее?? - person Lorence Hernandez; 04.12.2016
comment
@LorenceHernandez На SO много вопросов, в которых говорится о нарезке, не стоит повторять это еще раз. Добавил ссылку на нужный. - person skypjack; 04.12.2016