今天聊聊一个新接触的概念Double Dispatch
,翻译过来就是双重调度。
调度
对于首次接触,或者之前知道但是不知道是这个概念的开发人员来说,还是有必要把这块单拎出来讲下。
调度(Dispatching)
字面上的意思是将某物发送到一个目的地。在 C++ 中,这个词同样具有相同的含义。当你从主方法调用一个函数或方法时,程序会进入一个不同的地址空间并开始执行该方法。这一过程被称为调度
。
如果你调用的指定方法不是虚函数,那么该方法将在编译时被选择。这种方式被称为静态调度(Static Dispatch)
。在这种情况下,编译器在编译阶段就能确定应该调用哪个函数,因此没有运行时开销。
另一方面,如果你调用的指定方法是虚函数,那么只有在运行时才能确定究竟调用的是哪个函数。这种情况被称为动态调度”(Dynamic Dispatch)
。动态调度允许程序在运行时根据对象的实际类型决定调用哪个函数,这为多态性提供了支持。
在字面意义上,静态调度和动态调度都属于单重调度(Single Dispatch)
。单重调度的概念意味着调度机制只依赖于单个对象的类型来决定调用的函数。
一言以蔽之,调度就是我们常说的函数调用,分为静态调度和动态调度两种,静态调度是可以在编译阶段确定进入该函数地址进行调用的地址,而动态调度则指的是只有在运行阶段才能确定进入函数的地址。
以上,无论是动态调度还是静态调度,都统称为单重调度,接着,我们分析下本文的核心内容-双重调度。
双重调度
在前面内容中,我们大致降了调度的基本概念,提到了我们通常意义上的函数调用属于单重调度
,现在我们来借助虚函数来实现一个所有的**双重调度(这个双重调度是代码意义上,语言本身是否支持不能保证)**,代码如下:
class Bird
{};
classChicken:publicBird
{};
classAnimal
{
public:
virtual void eats(Bird *b)
{
cout <<"Animal Eats Bird"<< endl;
}
virtual void eats(Chicken *b)
{
cout <<"Animal Eats Chicken"<< endl;
}
};
classTiger:publicAnimal
{
public:
void eats(Bird *b)
{
cout <<"Tiger Eats Bird"<< endl;
}
void eats(Chicken *b)
{
cout <<"Tiger Eats Chicken"<< endl;
}
};
int main()
{
Animal*tiger =newTiger();
Bird*chicken =newChicken();
tiger->eats(chicken);
return0;
}
上面代码还是很简单的,那么能否说下运行结果呢?
我相信很多人跟我一样,第一个反应是Tiger Eats Chicken
,可惜的是编译器输出结果是Tiger Eats Bird
。
在上面这段代码中,两个不同类型的基类指针tiger和chicken分别指向对应的派生类Tiger和Chicken。然后我们执行tiger->eats(chicken),这个过程中设计两个虚函数调用,也即两个虚函数表,即Animal->Tiger和Bird->Chicken。
假如,C++能够正确执行两次调度,即执行从Animal指针到Tiger指针和Bird指针到Chicken指针的正确转换,即如果能够调用**Animal::eats(Chicken *b),那么输出即为我们所预期,可惜的是C++本身不支持双重调度
,也就是说之所有编译器输出``Tiger Eats Bird`,是因为其仅支持单重调度即Animal->Tiger。