AndyLi
AndyLi

Reputation: 81

Why do we allow the base class to cast to the derived class?

We all know that the base class can't be converted to a derived class, but as the code shows, we did, and we got the result B::foo() and A::fun(), how to interpret this Situation? (Why are B::foo() and A::fun()?)

#pragma
#include "pch.h"
#include<iostream>
using namespace std;

class A
{
public:
    void foo()
    {
        printf("A::foo()\n");
    }
    virtual void fun()
    {
        printf("A::fun()\n");
    }
};
class B : public A
{
public:
    void foo()
    {
        printf("B::foo()\n");
    }
    void fun()
    {
        printf("B::fun()\n");
    }
};
int main(void)
{
    A a;
    B *pb = (B*)&a;
    pb->foo();
    pb->fun();
    return 0;
}

Upvotes: 5

Views: 115

Answers (3)

freakish
freakish

Reputation: 56477

We all know that the base class can't be converted to a derived class

That's not 100% true. Well, it is true as stated. But then in your example you show us "pointer casts", not "type casts". And these are a bit different. This code is completely valid:

B b;
A* a = (A*)&b;
B* b2 = (B*)a;

The third line is correct, because we know that a is actually pointing to an instance of B. This behaviour is sometimes useful (e.g. a dispatcher).

but as the code shows, we did, and we got the result B::foo() and A::fun(), how to interpret this Situation?

Your code, on the other hand has undefined behaviour inside it. You start with object of type A, you take A* and you downcast it to B*. The compiler will allow you to do that because in general, as I said earlier this actually can be a correct code under certain circumstances (and it is hard for a compiler to detect these). But not in your case, in your case the result of this operation is not defined in terms of the C++ standard.

All in all: you should interpret it as an incorrect code that does random and dangerous stuff. And it needs fixing.

Upvotes: 2

Kaldrr
Kaldrr

Reputation: 2850

Your example invokes undefined behavior by invalid pointer cast.

A a;
B *pb = (B*)&a;

As you are using a C-style cast, it's hard to see which cast is exactly used, that's why C++ introduced specific casts. static_cast and reinterpret_cast both result in UB after any usage of pb, as variable a is NOT an instance of B. Anything that happens from here on now is compilers choice, it can crash, it can work, etc.

As your A class has a virtual method, you can use dynamic_cast, to check at runtime if your pointer points to an instance of B.

if(B* ptr = dynamic_cast<B*>(&a){
   // Use ptr, we know it's not null here
}

dynamic_cast will return a nullptr if the cast fails.

Upvotes: 1

Frederik Juul
Frederik Juul

Reputation: 301

This is undefined behavior territory.

You get the same behavior by declaring the B::fun() to be virtual and removing the inheritance from A.

#include<iostream>
using namespace std;

class A
{
public:
    void foo()
    {
        printf("A::foo()\n");
    }
    virtual void fun()
    {
        printf("A::fun()\n");
    }
};
class B
{
public:
    void foo()
    {
        printf("B::foo()\n");
    }
    virtual void fun()
    {
        printf("B::fun()\n");
    }
};
int main(void)
{
    A a;
    B *pb = (B*)&a;
    pb->foo();
    pb->fun();
    return 0;
}

This is likely due to the behavior of the VTable. Since A has a virtual function, it has __vptr storing the location of the VTable, where virtual functions can be looked up. When the void fun() function becomes virtual in B, it means that it follows the __vptr to check the VTable for a pointer to the void fun() function. Since the __vptr in A points to the A VTable, it follows the the pointer to void fun() will point to A::fun().

In other words, you're playing with the how the internal representation of things happen to look and getting lucky based on this internal representation. Similarly pointing a B* and any other piece of memory could work if the memory happens to contain similar enough information to a B that the program could work with it. It will however fail if you try to do more complex stuff (such as accessing variables defined in B).

Upvotes: 3

Related Questions