Reputation: 1533
I wonder how dynamic dispatching really works in C++. To illustrate my question, I will start by some Java code.
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);
}
}
The call a.op(2, 4)
will print "c", since indeed the compiler:
A
(since a
is declared to be a variable of type A
) which method is the closest to op(int, int)
,op(int, int)
method but finds the method op(int, double)
(with a single auto-cast int
-> double
),During the execution, the JVM:
op(int, double)
fixed by the compiler into Class C
but doesn't find it,B
,op(int, double)
, then calls it.The same principle applies to the call b.op(2.0, 4.0)
, which prints "b".
Now, consider the equivalent code in 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)
will print "c", like Java. But b->op(2.0, 4.0)
outputs "c" again, and there, I'm lost.
What are exactly the rules applied at the compilation and during the execution in C++ for dynamic dispatching?
(Notice you will have the same behavior from the C++ code if you write virtual
in front of each function; it changes nothing here)
Upvotes: 10
Views: 792
Reputation: 372
The compiler will warn/error on conversions if you tell it to. Using gcc, the compiler arguments -Wconversion -Werror
will prevent your code from compiling, as you are right, there is a potential loss in precision here.
Given that you didn't turn on this compiler option, the compiler is happy to resolve your call to b->op(double, double) to B::op(int, double).
Please keep in mind that this is a compile time decision - not a runtime/polymorphic decision.
The actual vtable of the "b" pointer will have a method op(int, int) available at runtime, but the compiler doesn't know about this method at compile time. It only can assume that the b pointer is of type B*.
Upvotes: 1
Reputation: 796
You start with the polymorphic behavior in your base class A
. Then, using the same signature, you cannot stop this in the derived classes.
It's not necessary, if you declare the same method virtual
or not.
You have to change the signature!
Furthermore, you have the visibility problem. This line
B *b = new C;
b->op(2.0, 4.0);
The compiler looks for a method within your class B
. The op
-methods hide the methods with the same name of class A
(overload resolution). If he founds something useful, he is just using it.
Upvotes: 0
Reputation: 62532
By declaring a new overload to op
in B
you have hidden the base versions. The compiler will only dispatch based on 'B' which is why it selects op(int,double)
.
Upvotes: 1
Reputation: 6822
For C++, when you do b->op(2.0, 4.0);
the compiler looks in B
, finds a method it can call (int x, double y)
and uses it. It does not look in the superclass if any method in the subclass can handle the call. This is called method hiding, ie. op(double, double)
is hidden.
If you want to make it select the (double x, double y)
version, you need to make the function visible inside of B
with the following declaration inside B
:
using A::op;
Further explanation of the rules
Upvotes: 3