在c ++中动态调度的规则是什么?

时间:2013-09-20 09:24:18

标签: c++ polymorphism

我想知道动态调度在C ++中是如何工作的。为了说明我的问题,我将从一些Java代码开始。

class A
{
  public void op(int x, double y) { System.out.println("a"); }
  public void op(double x, double y) { System.out.println("b"); }
}

class B extends A
{
  public void op(int x, double y) { System.out.println("c"); }
  public void op(int x, int y) { System.out.println("d"); }
}

class C extends B
{
  public void op(int x, int y) { System.out.println("e"); }
}

public class Pol
{
  public static void main(String[] args)
  {
    A a = new C();
    B b = new C();

    /* 1 */ a.op(2, 4);
    /* 2 */ b.op(2.0, 4.0);
  }
}

调用a.op(2, 4)将打印“c”,因为确实是编译器:

  • 查看类A(因为a被声明为A类型的变量),哪种方法最接近op(int, int)
  • 无法找到op(int, int)方法但找到方法op(int, double)(使用单个自动投标int - > double),
  • 然后修复此签名。

在执行期间,JVM:

  • 查找编译器将op(int, double)修复为类C的方法,但找不到它,
  • 查看C的超类,即B
  • 最后找到方法op(int, double),然后调用它。

同样的原则适用于打印“{”的电话b.op(2.0, 4.0)

现在,考虑C ++中的等效代码

#include <iostream>

class A
{
public:
  virtual void op(int x, double y) { std::cout << "a" << std::endl; }
  virtual void op(double x, double y) { std::cout << "b" << std::endl; }
};

class B : public A
{
public:
  void op(int x, double y) { std::cout << "c" << std::endl; }
  virtual void op(int x, int y) { std::cout << "d" << std::endl; }
};

class C : public B
{
public:
  void op(int x, int y) { std::cout << "e" << std::endl; }
};

int main()
{
  A *a = new C;
  B *b = new C;

  /* 1 */ a->op(2,  4);
  /* 2 */ b->op(2.0, 4.0);

  delete a;
  delete b;
}

a->op(2, 4)将打印“c”,就像Java一样。但b->op(2.0, 4.0)再次输出“c”,在那里,我迷失了。

在C ++中用于动态调度的编译和执行期间应用的规则是什么? (请注意,如果在每个函数前面编写virtual,您将从C ++代码中获得相同的行为;此处不会更改任何内容。

4 个答案:

答案 0 :(得分:3)

对于C ++,当b->op(2.0, 4.0);编译器查找B时,找到一个可以调用(int x, double y)并使用它的方法。如果子类中的任何方法都可以处理调用,它不会查看超类。这称为方法隐藏,即。隐藏op(double, double)

如果您想要选择(double x, double y)版本,则需要在B内显示该功能,并在B内使用以下声明:

using A::op;

Further explanation of the rules

答案 1 :(得分:1)

通过在op中向B声明新的重载,您隐藏了基本版本。编译器只会根据'B'进行调度,这就是它选择op(int,double)的原因。

答案 2 :(得分:1)

如果您告诉,编译器将对转换发出警告/错误。使用gcc,编译器参数-Wconversion -Werror将阻止您的代码编译,因为您是对的,这里可能存在精度损失。

鉴于您没有启用此编译器选项,编译器很乐意将您对b-&gt; op(double,double)的调用解析为B :: op(int,double)。

请记住,这是编译时决定 - 而不是运行时/多态决定。

“b”指针的实际vtable将在运行时提供方法op(int,int),但编译器在编译时不知道此方法。它只能假设b指针是B *类型。

答案 3 :(得分:0)

您从基类A中的多态行为开始。然后,使用相同的签名,您无法在派生类中停止此操作。

如果您声明相同的方法virtual,则没有必要。

您必须更改签名!

此外,您还有可见性问题。这一行

B *b = new C;
b->op(2.0, 4.0);

编译器在类B中查找方法。 op - 方法隐藏具有相同名称类A的方法(重载决策)。如果他找到了有用的东西,他就是在使用它。

相关问题