首先要理解 Dispatch 是什么。
多态的实现依赖虚表(vtable)完成运行时决策——真正调用哪个函数,由运行时的实际对象类型决定。这一过程称为一次 dispatch,即实现了一次动态多态。
Double dispatch 的典型应用场景是设计模式中的 Visitor 模式(参考:http://my.oschina.net/kkkkkkkkkkkkk/blog/670610)。
问题:重载在编译期绑定
先看下面这段代码存在的问题:
class Monster;
class FlyMonster;
class WalkMonster;
class MonsterMgr
{
public:
void Add(Monster*monster)
{
cout << "Monster" << endl;
}
void Add(FlyMonster*fly)
{
cout << "fly" << endl;
}
void Add(WalkMonster*walk)
{
cout << "walk" << endl;
}
};
class Monster
{
public:
};
class FlyMonster:public Monster
{
public:
};
class WalkMonster :public Monster
{
public:
};
int main(int argc, char *argv[])
{
Monster *monster = new FlyMonster;
MonsterMgr *mgr = new MonsterMgr;
mgr->Add(monster);
system("pause");
return 0;
}
由于 Add 有多个重载版本,而重载决议在编译期就已确定——变量 monster 的声明类型是 Monster*,所以编译器绑定的是 Monster 版本的 Add,而不是我们期望的 FlyMonster 版本。
解决方案:引入第二次 Dispatch
为了达到目的,需要再引入一次 dispatch。原本的设计是由 MonsterMgr 主动添加怪物,现在改为由怪物自己把自身添加到 MonsterMgr 中,借助虚函数触发第二次 dispatch。这样,子类的 this 指针才是真正的运行时对象,Add 就能正确匹配到 FlyMonster 版本。
这种设计有一种"反转控制"的感觉。C++ 本身不直接支持 double dispatch,通过这种方式便可以优雅地实现:
class Monster;
class FlyMonster;
class WalkMonster;
class MonsterMgr
{
public:
void Add(Monster*monster)
{
cout << "Monster" << endl;
}
void Add(FlyMonster*fly)
{
cout << "fly" << endl;
}
void Add(WalkMonster*walk)
{
cout << "walk" << endl;
}
};
class Monster
{
public:
virtual void AddToMgr(MonsterMgr*m)
{
m->Add(this);
}
};
class FlyMonster:public Monster
{
public:
virtual void AddToMgr(MonsterMgr*m)
{
m->Add(this);
}
};
class WalkMonster :public Monster
{
public:
virtual void AddToMgr(MonsterMgr*m)
{
m->Add(this);
}
};
int main(int argc, char *argv[])
{
Monster *monster = new FlyMonster;
MonsterMgr *mgr = new MonsterMgr;
monster->AddToMgr(mgr);
system("pause");
return 0;
}