The issue here is that you are using a C cast (T) expr
, which is 99% of the time a bad idea in C++.
C casts only exist in C++ due to the need of being retrocompatible with C, and can behave in unexpected ways.
From here:
When the C-style cast expression is encountered, the compiler attempts to interpret it as the following cast expressions, in this order:
a) const_cast<new_type>(expression);
b) static_cast<new_type>(expression), with extensions: pointer or reference to a derived class is additionally allowed to be cast to pointer or reference to unambiguous base class (and vice versa) even if the base class is inaccessible (that is, this cast ignores the private inheritance specifier). Same applies to casting pointer to member to pointer to member of unambiguous non-virtual base;
c) static_cast (with extensions) followed by const_cast;
d) reinterpret_cast<new_type>(expression);
e) reinterpret_cast followed by const_cast.
The first choice that satisfies the requirements of the respective cast operator is selected, even if it cannot be compiled (see example)
The correct type of cast when downcasting in C++ is dynamic_cast<T>(expr)
, which checks if the object of the expression can be cast to the derived type T before performing it. If you did that, you would have got a compile time or runtime error, instead of getting a wrong behaviour.
C-style casts never perform dynamic casts, so (B2*)
in B2* b2 = (B2*)a
becomes equivalent to reinterpret_cast<B2*>
which is a type of cast that blindly coerces any pointer type to any other. In this way C++ can't do any of the required pointer "magic" it's usually needed to convert a C* into a valid B2*.
Given that polymorphism in C++ is implemented through virtual dispatching using method tables, and that the pointer in b2
doesn't point to the correct base class (given that it was actually a pointer to B1), you are accessing the vtable
for B1 instead of B2 through b2
.
Both Return1
and Return2
are the first functions in the vtables of their respective abstract classes, so in your case Return1
is mistakenly called - you could largely approximate virtual invocations with something like b2->vtable[0]()
in most implementations. Given that neither of the two methods touch this
, nothing breaks and the function returns without crashing the program (which is not guaranteed, given this whole thing is undefined behaviour).