我想知道动态调度在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
的方法,但找不到它,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 ++代码中获得相同的行为;此处不会更改任何内容。
答案 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;
答案 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
的方法(重载决策)。如果他找到了有用的东西,他就是在使用它。