Понимание двойной отправки С++

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

#include<iostream>
using namespace std;

class Monster;
class Warrior;

class Creature{
public:
    virtual void fight(Creature&) =0;
};

class Monster: public Creature{
    void fightwho(Warrior& w) {cout<<"Monster versus Warrior"<<endl; }
    void fightwho(Monster& m) {cout<<"Monster versus Monster"<<endl; }
public:
    void fight(Creature& c)  {c.fightwho(*this);}
};

class Warrior: public Creature{
    void fightwho(Warrior& w) {cout<<"Warrior versus Warrior"<<endl; }
    void fightwho(Monster& m) {cout<<"Monster versus Warrior"<<endl; }
public:
    void fight(Creature& c) {c.fightwho(*this);}
};

int main()
{
Warrior w;
Monster m;
w.fight(m);
}

Это приводит к ошибке компилятора, которую я предвижу:

ex12_10.cpp: В функции-члене "virtual void Monster:: fight (Creature &): ex12_10.cpp: 17: 30: ошибка:" класс Существо не имеет имени с именем "fightwho

ex12_10.cpp: В функции члена "виртуальный void Warrior:: fight (Существо и amp;): ex12_10.cpp: 24: 29: ошибка:" класс Существо не имеет имени с именем "fightwho

Но я не знаю, как исходить отсюда... Пожалуйста, помогите.

Ответ 1

Ну, очевидно, у вас действительно нет fightwho, объявленного в вашем классе Creature, поэтому вам нужно объявить его там и объявить его как virtual.

Двойная отправка работает таким образом, что для вызова (это предполагает Warrior& w = ..., а не Warrior w):

w.fight(m);

Сначала виртуальный механизм будет выбирать Warrior::fight вместо Monster::fight, а затем механизм перегрузки будет выбирать Monster::fightwho(Warrior& m) вместо Warrior::fightwho(Warrior& m). Обратите внимание, что это имеет смысл, если бы вы имели бы:

Warrior w;
Monster m;
Creature& c1 = w;
Creature& c2 = m;
c1.fight(c2); // not w.fight(m)

Следовательно, метод, который в конечном итоге будет вызван, будет отправлен в соответствии с типом объекта, на который вы его называете, и типа объекта, отправленного в качестве аргумента, то есть двойной отправки

Кроме того, обратите внимание, что это не лучший пример, так как ваши типы являются членами одной и той же иерархии. Шаблон дизайна посетителя является хорошим примером реализации двойной отправки на языках, которые не поддерживают его как граждан первого класса (например, С++ и производные: Java, С#...)

Как правильно отмечает @CrazyCasta, когда ваша иерархия классов начинает расти, этот подход становится намного сложнее поддерживать и может привести к взрыву количества методов, поэтому тщательно выбирайте...

Ответ 2

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

#include <iostream>

using namespace std;

class A;
class A1;
class A2;
class B1;
class B2;

class B {
    public:
        // dispatcher function to A
        virtual void collide(const A& a) const = 0;

        // actual collision logic B with types of A
        virtual void collide(const A1& a) const = 0;
        virtual void collide(const A2& a) const = 0;
};

class A {
    public:
        // dispatcher function to B
        virtual void collide(const B& b) const = 0;

        // actual collision logic A with types of B
        virtual void collide(const B1& b) const = 0;
        virtual void collide(const B2& b) const = 0;
};

class A1 : public A {
    public:
        void collide(const B& b) const {
            // dispatch to b
            b.collide(*this);
        }
        void collide(const B1& b) const {
            cout << "collision with B1 and A1" << endl;
        }
        void collide(const B2& b) const {
            cout << "collision with B2 and A1" << endl;
        }
};

class A2 : public A {
    public:
        void collide(const B& b) const {
            // dispatch to a
            b.collide(*this);
        }
        void collide(const B1& b) const {
            cout << "collision with B1 and A2" << endl;
        }
        void collide(const B2& b) const {
            cout << "collision with B2 and A2" << endl;
        }
};

class B1 : public B {
    public:
        void collide(const A& b) const {
            b.collide(*this);
        }
        void collide(const A1& b) const {
            cout << "collision with A1 Bnd B1" << endl;
        }
        void collide(const A2& b) const {
            cout << "collision with A2 Bnd B1" << endl;
        }
};

class B2 : public B {
    public:
        void collide(const A& a) const {
            a.collide(*this);
        }
        void collide(const A1& a) const {
            cout << "collision with A1 Bnd B2" << endl;
        }
        void collide(const A2& a) const {
            cout << "collision with A2 Bnd B2" << endl;
        }
};

int main() {

    A* a = new A1();
    B* b = new B2();

    // first dispatch is done by polymorphism ( a is resolved as a A1 )
    // second dispatch is done in collide function by the function overloading
    // ( in collide function we are sending A1 to collide function of B )
    a->collide(*b);

}

Ответ 3

Если вы хотите это сделать, вам нужно будет использовать RTTI. Вам нужно будет проверить тип передаваемой вещи. В общем, это не лучший шаблон дизайна, который можно использовать, если вы можете его избежать. Если вы хотите взаимодействовать с двумя объектами, вы обычно хотите использовать стандартный интерфейс другого. Например, вы могли бы сказать, что creature.attack(other_creature) и атака могут запрашивать защиту другого существа, и на основе этого и его собственные статистики отправляют обновление hp другому_creature.