Почему приведение от базы к производной дает эту функциональность? - PullRequest
0 голосов
/ 05 сентября 2018

Меня смущает, почему приведение производного класса к указателю базового класса вызывает метод производного класса, когда я не использовал ключевое слово virtual. Это нормальное поведение? Разве указатель не хранит объект Person в памяти, поэтому приведение его к объекту Student не должно иметь никакого влияния на его содержимое?

  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;

}

Вывод кода следующий

  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

1 Ответ

0 голосов
/ 05 сентября 2018

Ваш код "работает", потому что ни один из ваших методов about_me() ни к чему не обращается к своим указателям this.

С технической точки зрения, вы делаете неопределенное поведение , потому что pperson не указывает на действительный Student объект, но вы говорите компилятору обрабатывать его так, как если бы он был. Так буквально может произойти все, что угодно .

В во многих распространенных реализациях компилятора вызов метода класса, подобный pperson->about_me(), на самом деле вызывает больше как about_me(pperson), где about_me() реализуется как отдельная функция с входным параметром this. Так что код, который вы показали, может быть реализацией компилятором, более похожим на это под капотом (не совсем, но вы должны понять):

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;
}

Итак, при четвертом вызове about_me() вы указываете компилятору вызывать Student::about_me() вместо того, чтобы позволить ему вызывать Person::about_me() в обычном режиме с параметром this, установленным на указатель Person*, который имеет тип -кастрируется до Student*. Поскольку this не разыменовывается about_me(), вызов является «успешным» в том смысле, что вы видите «ожидаемый» результат. Неважно, на что this указывает в этом случае, потому что он не используется.

Теперь попробуйте добавить некоторые элементы данных в ваши классы, а затем вывести эти элементы в about_me(), и вы увидите очень разные, очень неожиданные / случайные результаты из-за неопределенного поведения, которое вы вызываете. Например:

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

...