Florian Richoux
Florian Richoux

Reputation: 1533

What are the rules for dynamic dispatching in c++?

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:

During the execution, the JVM:

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

Answers (4)

Carl Cook
Carl Cook

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

Kai Walz
Kai Walz

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

Sean
Sean

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

Hampus Nilsson
Hampus Nilsson

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

Related Questions