Переопределенная функция скрывается даже после динамического приведения (подкаста)

#c #casting #dynamic-cast #static-cast

Вопрос:

Я начал узнавать о динамическом приведении и о том, как он использует RTTI для определения типа объекта для понижения. В этом примере я сделал переход от производного класса к базовому классу.

 #include <iostream>

using namespace std;


class Base
{
public:
    virtual void foo()
    {
        cout<<"Base"<<endl;
    }
};

class Derived : public Base
{
public:
    void foo()
    {
        cout<<"Derived"<<endl;
    }
};


int main()
{
    Derived* d = new Derived();

    Base* b = dynamic_cast<Base*>(d);        //(1)

    cout<<typeid(b).name()<<endl;
    b->foo();

    return 0;

} 
 

Программа печатает:

 PBase
Derived
 

Для меня:

  • В компиляции компилятор создает таблицу V для базового класса, где у нас есть указатель на «таблицу RTTI» и указатель на функцию Base::foo.
  • Во время выполнения, когда программа доходит до строки (1), он создает указатель _vptrBase, который указывает на эту виртуальную таблицу.

Мой вопрос: как получается, что программа выводит «Производное», когда мы вызываем функцию foo? Почему он не вызывает функцию Base::foo?

Комментарии:

1. Base* b = dynamic_cast<Base*>(d); равносильно справедливому Base* b = d; . Эта строка не касается указателя vtable внутри класса. Он указывал на Derived таблицу v, когда объект был создан (потому что это его тип), и он продолжает указывать на эту таблицу v после приведения.

2. если вы этого не хотите, то не делайте foo виртуальным. смотрите в прямом эфире

3. если вы действительно хотите виртуальный, но вам нужен вызов Base::foo , то вызовите его явно b->Base::foo(); . смотрите в прямом эфире

4. Для этого и нужен полиморфизм. У вас может быть указатель на базовый класс и методы вызова, определенные для производных версий. Лучшим примером может быть массив базовых указателей и несколько версий производных классов.

Ответ №1:

Цитирую cppreference (выделено жирным шрифтом):

Виртуальные функции-это функции-члены, поведение которых может быть переопределено в производных классах. В отличие от невиртуальных функций, поведение переопределения сохраняется, даже если во время компиляции отсутствует информация о фактическом типе класса. То есть, если производный класс обрабатывается с использованием указателя или ссылки на базовый класс, вызов переопределенной виртуальной функции вызовет поведение, определенное в производном классе. Такой вызов функции известен как вызов виртуальной функции или виртуальный вызов.