Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ virtual function not called in subclass

Consider this simple situation:

A.h

class A {
public:
    virtual void a() = 0;
};

B.h

#include <iostream>

class B {
public:
    virtual void b() {std::cout << "b()." << std::endl;};
};

C.h

#include "A.h"
#include "B.h"

class C : public B, public A {
public:
    void a() {std::cout << "a() in C." << std::endl;};
};

int main() {
    B* b = new C();
    ((A*) b)->a();    // Output: b().

    A* a = new C();
    a->a();           // Output:: a() in C.

   return 0;
}

In other words:
- A is a pure virtual class.
- B is a class with no super class and one non-pure virtual function.
- C is a subclass of A and B and overrides A's pure virtual function.

What surprises me is the first output i.e.

((A*) b)->a();    // Output: b().

Although I call a() in the code, b() is invoked. My guess is that it is related to the fact that the variable b is a pointer to class B which is not a subclass of class A. But still the runtime type is a pointer to a C instance.

What is the exact C++ rule to explain this, from a Java point of view, weird behaviour?

like image 230
Marc Avatar asked Jan 20 '10 21:01

Marc


2 Answers

You are unconditionally casting b to an A* using a C-style cast. The Compiler doesn't stop you from doing this; you said it's an A* so it's an A*. So it treats the memory it points to like an instance of A. Since a() is the first method listed in A's vtable and b() is the first method listed in B's vtable, when you call a() on an object that is really a B, you get b().

You're getting lucky that the object layout is similar. This is not guaranteed to be the case.

First, you shouldn't use C-style casts. You should use C++ casting operators which have more safety (though you can still shoot yourself in the foot, so read the docs carefully).

Second, you shouldn't rely on this sort of behavior, unless you use dynamic_cast<>.

like image 143
i_am_jorf Avatar answered Sep 24 '22 05:09

i_am_jorf


Don't use a C-style cast when casting across a multiple inheritance tree. If you use dynamic_cast instead you get the expected result:

B* b = new C();
dynamic_cast<A*>(b)->a();
like image 24
sth Avatar answered Sep 23 '22 05:09

sth