C++ 中的多重分派

Posted

技术标签:

【中文标题】C++ 中的多重分派【英文标题】:Multiple dispatch in C++ 【发布时间】:2010-12-17 11:57:11 【问题描述】:

我想了解什么是多次调度。我阅读了很多不同的文本,但我仍然不知道多重调度是什么以及它有什么好处。也许我缺少的是使用多次调度的一段代码。拜托,你能用 C++ 编写一小段代码,使用多次调度,这样我就可以看到它不能正确编译/运行,因为 C++ 只有一次调度?我需要看到区别。谢谢。

【问题讨论】:

C++ 不直接支持它,但我相信你可以以某种方式模仿它。我从来没有使用过 MD,甚至没有看到其他语言的优秀设计让我想在 C++ 中拥有 MD。迪伦粉丝将其列为迪伦的语言功能之一。但据我所知,它闻起来像是一个糟糕的设计,因为您可能必须编写的函数数量呈指数增长。我不想写那么多函数。 您在使用Visitor 模式的任何时候都使用过MD。 使用 C++11 的简单示例:ideone.com/lTsc7M 【参考方案1】:

多调度是根据传递给函数调用的参数的运行时类型选择调用哪个版本的函数的能力。

这是一个在 C++ 中无法正常工作的示例(未经测试):

class A  ;
class B : public A  ;
class C : public A  


class Foo

  virtual void MyFn(A* arg1, A* arg2)  printf("A,A\n"); 
  virtual void MyFn(B* arg1, B* arg2)  printf("B,B\n"); 
  virtual void MyFn(C* arg1, B* arg2)  printf("C,B\n"); 
  virtual void MyFn(B* arg1, C* arg2)  printf("B,C\n"); 
  virtual void MyFn(C* arg1, C* arg2)  printf("C,C\n"); 
;

void CallMyFn(A* arg1, A* arg2)

  // ideally, with multi-dispatch, at this point the correct MyFn() 
  // would be called, based on the RUNTIME type of arg1 and arg2
  pFoo->MyFn(arg1, arg2);


...

A* arg1 = new B();
A* arg2 = new C();
// Using multi-dispatch this would print "B,C"... but because C++ only
// uses single-dispatch it will print out "A,A"
CallMyFn(arg1, arg2);

【讨论】:

谢谢,这个答案正是我需要看到的。现在我只需要找出为什么有人需要这样的东西。无论如何,谢谢你的好例子。 一个应用是物理学,一个立方体与另一个立方体碰撞是一个交集,一个立方体与一个平面碰撞是另一个交集。因此,您最终会得到很多不同的碰撞检测方法,而调度对此非常有用。这是双重调度的线程,gamedev.net/topic/453624-double-dispatch-in-c 另一个应用程序是编程语言。假设您希望加号运算符 ('+') 在两个参数都是整数时计算整数结果,当两个参数都是浮点数时计算浮点数,当两个参数都是字符串时计算连接字符串。您有一个从“表达式”派生的类型层次结构,因此您希望调用 add(exp1,exp2) 或 expr1->add(expr2) 时调用的函数取决于 exp1 和 exp2 的实际类型。【参考方案2】:

多重分派是指执行的函数取决于多个对象的运行时类型。

C++ 具有单一调度,因为当您使用虚函数时,实际运行的函数仅取决于 -> 或 .运算符。

我很难想出一个真正的多分派编程案例。也许在一个不同角色互相战斗的游戏中。

void Fight(Opponent& opponent1, Opponent& opponent2);

战斗的获胜者可能取决于双方对手的特征,因此您可能希望此调用分派到以下之一,具体取决于两个参数的运行时类型:

void Fight(Elephant& elephant, Mouse& mouse)

    mouse.Scare(elephant);


void Fight(Ninja& ninja, Mouse& mouse)

    ninja.KarateChop(mouse);


void Fight(Cat& cat, Mouse& mouse)

    cat.Catch(mouse);


void Fight(Ninja& ninja, Elephant& elephant)

    elephant.Trample(ninja);


// Etc.

函数的作用取决于两个参数的类型,而不仅仅是一个。在 C++ 中,您可能必须将其编写为一些虚函数。将根据一个参数(this 指针)选择一个虚函数。然后,虚函数可能需要包含一个开关或一些东西来对另一个参数做一些特定的事情。

【讨论】:

一个实际的、经常发生的例子是将不同的子类与基类指针数组区别对待。【参考方案3】:

单次调度中,执行的函数仅取决于对象类型。在 double dispatch 执行的函数取决于对象类型和 范围。

在以下示例中,函数 Area() 使用以下命令调用 单分派,Intersect() 依赖双分派,因为它需要一个 形状参数。

class Circle;
class Rectangle;
class Shape

    virtual double Area() = 0; // Single dispatch

    // ...
    virtual double Intersect(const Shape& s) = 0; // double dispatch, take a Shape argument
    virtual double Intersect(const Circle& s) = 0; 
    virtual double Intersect(const Rectangle& s) = 0; 
;

struct Circle : public Shape

    virtual double Area()  return /* pi*r*r */; 

    virtual double Intersect(const Shape& s); 
     return s.Intersect(*this)  ; 
    virtual double Intersect(const Circle& s); 
     /*circle-circle*/ 
    virtual double Intersect(const Rectangle& s); 
     /*circle-rectangle*/ 
;

示例基于此article。

【讨论】:

你有相当多的空间。 @strager 2 个额外空格 :) 如果 Stack Overflow 可以将其格式化为查看者喜欢的空格数,那就太好了。

以上是关于C++ 中的多重分派的主要内容,如果未能解决你的问题,请参考以下文章

c++中的多重定义错误

C++中的多重继承逻辑

C++ 中的多重继承导致难以覆盖通用功能

处理多种类型和数组时如何编写“好”的 Julia 代码(多重分派)

C++中的多重插入运算符<<

C++反汇编第四讲,认识多重继承,菱形继承的内存结构,以及反汇编中的表现形式.