Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

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;
}
like image 825
AndyLi Avatar asked Nov 19 '19 09:11

AndyLi


People also ask

Can you cast from base class to derived?

When we create an instance of a base class, its data members are allocated in memory, but of course data members of inherited classes are not allocated. So, downcasting from a base to a derived class is not possible because data members of the inherited class are not allocated.

Why would one create a base class object with reference to the derived class?

One reason for this could be that BaseClass is abstract (BaseClasses often are), you want a BaseClass and need a derived type to initiate an instance and the choice of which derived type should be meaningful to the type of implementation.

Why a base class pointer can point to its derived class object but a derived class pointer Cannot point to its base class object?

Explanation: A base class pointer can point to a derived class object, but we can only access base class member or virtual functions using the base class pointer because object slicing happens when a derived class object is assigned to a base class object.

What is the purpose of using derived class?

Derived classes are used for augmenting the functionality of base class by adding or modifying the properties and methods to suit the requirements of the specialization necessary for derived class.


2 Answers

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).

like image 56
Frederik Juul Avatar answered Oct 15 '22 15:10

Frederik Juul


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.

like image 22
freakish Avatar answered Oct 15 '22 13:10

freakish