Search code examples
c++castingradixderived

Why is casting from base to derived giving this functionality?


I am confused as to why casting a derived class to a pointer of base class calls upon the derived class method when I haven't used the virtual keyword. Is this normal behavior? Doesn't the pointer hold a Person object in memory, therefore casting it to a Student should not have any affect to its contents?

  class Person {

    public:

    Person()
    {
        cout << "Creating Person Class" << endl;
    }

    void about_me()
    {
        cout << "I am a person" << endl;
    }
};

class Student : protected Person {
    public:
    Student()
    {
        cout << "Creating Student Class" << endl;
    }

    void about_me()
    {
        cout << " I am a student " << endl;
    }

};

int main()
{
    Person* pperson = new Person();
    Student* pstudent = new Student();

    pperson->about_me();
    pstudent->about_me();

    pperson-> about_me();

    ((Student*)pperson)-> about_me(); // this is the line where I do the cast

    return 0;

}

The output of the code is the following

  Creating Person Class
Creating Person Class
Creating Student Class
I am a person
 I am a student 
I am a person
 I am a student

Solution

  • Your code "works" because neither of your about_me() methods access their respective this pointers for anything.

    Technically speaking, what you are doing is undefined behavior, because pperson does not point at a valid Student object, but you are telling the compiler to treat it as if it were. So literally anything can happen.

    In many common compiler implementations, a class method call like pperson->about_me() is actually calling more like about_me(pperson), where about_me() gets implemented as a standalone function with a this input parameter. So code you have shown might be implementation by the compiler more like this under the hood (not exactly, but you should get the idea):

    struct Person
    {
    };
    
    void Person_Person(Person *this)
    {
        cout << "Creating Person Class" << endl;
    }
    
    void Person_about_me(Person *this)
    {
        cout << "I am a person" << endl;
    }
    
    struct Student
    {
    };
    
    void Student_Student(Student *this)
    {
        Person_Person(this);
        cout << "Creating Student Class" << endl;
    }
    
    void Student_about_me(Student *this)
    {
        cout << " I am a student " << endl;
    }
    
    int main()
    {
        //Person* pperson = new Person();
        byte *buf1 = new byte[sizeof(Person)];
        Person* pperson = (Person*) buf1;
        Person_Person(pperson);
    
        //Student* pstudent = new Student();
        byte *buf2 = new byte[sizeof(Student)];
        Student* pstudent = (Student*) buf2;
        Student_Student(pstudent);
    
        //pperson->about_me();
        Person_about_me(pperson);
    
        //pstudent->about_me();
        Student_about_me(pstudent);
    
        //pperson-> about_me();
        Person_about_me(pperson);
    
        //((Student*)pperson)-> about_me();
        Student_about_me((Student*)pperson);
    
        return 0;
    }
    

    So, in the 4th call to about_me(), you are instructing the compiler to call Student::about_me() instead of letting it call Person::about_me() normally, with its this parameter set to a Person* pointer that is type-casted to Student*. Since this is not dereferenced by about_me(), the call is "successful" in that you see the "expected" output. It doesn't matter what this points to in this case, because it is not being used.

    Now, try adding some data members to your classes, and then output those members in about_me(), and you will see very different, very unexpected/random results, due to the undefined behavior you are invoking. For example:

    class Person
    {
    protected:
        string m_name;
    
    public:
    
        Person(const string &name)
            : m_name(name)
        {
            cout << "Creating Person Class" << endl;
        }
    
        void about_me()
        {
            cout << "I am a person, my name is " << m_name << endl;
        }
    };
    
    class Student : protected Person
    {
    private:
        int m_id;
        string m_school;
    
    public:
    
        Student(const string &name, int id, const string &school)
            : Person(name), m_id(id), m_school(school)
        {
            cout << "Creating Student Class" << endl;
        }
    
        void about_me()
        {
            cout << "I am a student, my name is " << m_name << ", my id is " << m_id << " at " << m_school << endl;
        }
    };
    
    int main()
    {
        Person* pperson = new Person("John Doe");
        Student* pstudent = new Student("Jane Doe", 12345, "Some School");
    
        pperson->about_me(); // "I am a person, my name is John Doe"
        pstudent->about_me(); // "I am a student, my name is Jane Doe, my id is 12345 at Some School"
    
        pperson->about_me(); // "I am a person, my name is John Doe"
    
        ((Student*)pperson)->about_me(); // runtime error!
    
        delete pstudent;
        delete pperson;
    
        return 0;
    }
    

    Live Demo