我想知道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)
,它打印出 "b"。
现在考虑等效的 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)
将像 Java 一样打印 "c"。但是 b->op(2.0, 4.0)
再次输出 "c",我对此感到迷惑。
C++ 中在编译期和执行期间应用于动态分派的确切规则是什么?
(请注意,如果在每个函数前面写上virtual
,则 C++ 代码将具有相同的行为;在这里不会改变任何东西)
b->op(2.0, 4.0)
我们正在请求一个带有两个浮点数参数的函数。编译器怎么会认为选择一个带有op(int, double)
签名的函数是个好主意呢?它不能(不应该?)从double
自动转换为int
;这样会有精度损失的风险。 - Florian Richouxdouble
隐式可转换为int
,且void (int, double)
比void (int, int)
更匹配。A
中的void (double, double)
从未被看到,因为一旦在B
中找到op
,名称查找就结束了。 - SimpleB
没有定义op
,那么名称查找将无法在B
中找到它,因此它会在基类中查找。这种情况在任何范围内都会发生;类在这方面并不特殊。以全局函数foo
和命名空间中的一个函数为例;如果您调用foo
,它会在您的命名空间中找到该函数,然后停止,从未找到全局的foo
函数(忽略ADL)。 - Simple