问题 了解双重调度C ++


我试着理解双重调度的工作原理。我创建了一个例子,其中一个怪物和一个来自抽象类生物的战士可以战斗。类Creature有方法“战斗”,它是在派生类中定义的,并且在每个派生类中定义了如果战士与战士或与怪物等战斗会发生什么。我写了下面的代码:

#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:错误:'class Creature'没有名为'fightwho'的成员

ex12_10.cpp:在成员函数'virtual void Warrior :: fight(Creature&)':ex12_10.cpp:24:29:错误:'class Creature'没有名为'fightwho'的成员

但我不知道怎么从这里开始...请帮助。


4793
2017-09-25 11:29


起源

您关注哪本书/教程? - green diod


答案:


嗯,显然,你真的没有 fightwho 宣告你的 Creature class,所以你需要在那里声明它,并声明它 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)

因此,最终调用的方法将根据您调用它的对象的类型和作为参数发送的对象的类型进行调度,即 双重调度

此外,请注意,这可能不是最佳示例,因为您的类型是同一层次结构的成员。访问者设计模式是语言中双重调度实现的一个很好的例子,它不支持它作为一等公民(即C ++和衍生物:Java,C#......)

正如@CrazyCasta正确指出的那样,当您的类层次结构开始增长时,这种方法变得更难维护并且可能导致方法数量激增,因此请谨慎选择......


8
2017-09-25 11:34



当我将这一行添加到类Creature:virtual void fightwho(Creature&);时,我得到了/tmp/cceFWViM.o:在函数中 Creature::Creature()': ex12_10.cpp:(.text._ZN8CreatureC2Ev[_ZN8CreatureC5Ev]+0xf): undefined reference to vtable for Creature'/ tmp /cceFWViM.o :(。rodata._ZTV7Warrior [vtable for Warrior] + 0x18):undefined reference to Creature::fightwho(Creature&)' /tmp/cceFWViM.o:(.rodata._ZTV7Monster[vtable for Monster]+0x18): undefined reference to Creature :: fightwho(生物&)'/ tmp /cceFWViM.o :(。rodata._ZTI7Warrior [typeinfo for Warrior] + 0x10):未定义引用`typeinfo for Creatur - gartenzwerg
你需要将它声明为纯虚拟或提供正文。 - Zdeslav Vojkovic
如果我添加到Creature“virtual void fightwho(Warrior&)= 0; virtual void fightwho(Monster&)= 0;”。但是如果没有在Creature中命名Monster和Warrior,是否可以实现?如果我添加“virtual void fightwho(Creature&)= 0;”这是行不通的 - gartenzwerg
不,这是不可能的,你需要在生物中战斗(战士&)和战斗(怪物&),因此为什么这会很快变得非常讨厌。 - CrazyCasta
@CrazyCasta你当然是对的。我注意到了失踪 =0 完全忽略了这个论点 - Zdeslav Vojkovic


我对上述答案的贡献是提供经过充分测试的例子,以便澄清现实中的双重派遣概念。如果您查看以下代码,您将找到答案 我怎么能自己实现

#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);

}

2
2017-12-17 06:06





如果你想这样做,你需要使用RTTI。您需要检查传入的东西的类型。一般来说,如果您可以避免使用它,那么这不是最好的设计模式。如果要交互两个对象,通常需要使用另一个对象的标准接口。例如,你可能会说creature.attack(other_creature)和攻击可能会查询另一个生物的防御,并根据它和它自己的统计数据发布对其他生物的hp更新。


0
2017-09-25 11:33



双重调度的目的是避免基于RTTI的显式类型选择。 - Zdeslav Vojkovic
对于要与之交互的每种类型的类,您的方法都需要基类中的抽象函数(最有可能的是,每种类型都派生自基类)。这很快变得非常丑陋。 - CrazyCasta
我完全同意 - 这就是为什么我不是特别喜欢访客模式,特别是当层次结构开始增长时 - 它很快变成一团糟。 - Zdeslav Vojkovic
我也编辑了我的答案,强调你提出的观点。 - Zdeslav Vojkovic
我不知道在C ++(和派生语言)中使用双重调度的更好选项,因为您需要显式提供所有组合。 - Zdeslav Vojkovic