Переопределение по сравнению с виртуальным

Что цель использовать зарезервированное слово, виртуальное перед функциями? Если я хочу, чтобы дочерний класс переопределил родительскую функцию, я просто объявляю ту же функцию такой как void draw(){}.

class Parent { 
public:
    void say() {
        std::cout << "1";
    }
};

class Child : public Parent {
public:
    void say()
    {
        std::cout << "2";
    }
};

int main()
{
    Child* a = new Child();
    a->say();
    return 0;
}

Вывод равняется 2.

Таким образом, снова, почему был бы зарезервированное слово virtual будьте необходимы в заголовке say() ?

Огромное спасибо.

39
задан nonsensickle 17 February 2014 в 22:08
поделиться

6 ответов

Я думаю, это классический вопрос о том, как работает полиморфизм. Основная идея заключается в том, что вы хотите абстрагироваться от конкретного типа для каждого объекта. Другими словами: вы хотите иметь возможность вызывать экземпляры Child, не зная, что это дочерний элемент!

Вот пример: Предполагая, что у вас есть классы «Child» и «Child2» и «Child3», вы хотите иметь возможность ссылаться на них через их базовый класс (Parent).

Parent* parents[3];
parents[0] = new Child();
parents[1] = new Child2();
parents[2] = new Child3();

for (int i=0; i<3; ++i)
    parents[i]->say();

Как вы понимаете, это очень мощно. Это позволяет вам расширять родительский объект столько раз, сколько вы хотите, и функции, которые принимают родительский указатель, по-прежнему будут работать. Чтобы это работало, как упоминают другие, вам необходимо объявить метод как виртуальный.

18
ответ дан 27 November 2019 в 02:11
поделиться

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

2
ответ дан 27 November 2019 в 02:11
поделиться

Если вы не используете ключевое слово virtual, вы не переопределяете, а скорее определяете несвязанный метод в производном классе, который будет скрывать метод базового класса. То есть, без virtual, Base::say и Derived::say не связаны - кроме совпадения имен.

Когда вы используете ключевое слово virtual (обязательное в базовом, необязательное в производном классе), вы сообщаете компилятору, что классы, производные от этой базы, смогут переопределить этот метод. В этом случае Base::say и Derived::say считаются переопределением одного и того же метода.

Когда вы используете ссылку или указатель на базовый класс для вызова виртуального метода, компилятор добавит соответствующий код, чтобы был вызван конечный переопределитель (переопределение в самом производном классе, который определяет метод в иерархии используемого конкретного экземпляра). Обратите внимание, что если вы используете не ссылки/указатели, а локальные переменные, компилятор может разрешить вызов и ему не нужно использовать механизм виртуальной диспетчеризации.

16
ответ дан 27 November 2019 в 02:11
поделиться

Если бы функция была виртуальной, то вы могли бы сделать это и получить на выходе "2":

Parent* a = new Child();
a->say();

Это работает, потому что виртуальная функция использует фактический тип, тогда как невиртуальная функция использует объявленный тип. Почитайте о полиморфизме, чтобы лучше понять, зачем это нужно.

37
ответ дан 27 November 2019 в 02:11
поделиться

Попробуйте с:

Parent *a = new Child();
Parent *b = new Parent();

a->say();
b->say();

Без virtual, оба с print '1'. Добавьте virtual, и дочерний элемент будет вести себя как Child, даже если на него ссылаются через указатель на Parent.

26
ответ дан 27 November 2019 в 02:11
поделиться

Это очень важный аспект программирования на c++ - почти на каждом собеседовании, на котором я был, мне задавали этот вопрос.

Что произойдет, если вы измените main на:

int main() { Parent* a = new Child(); a->say(); return 0; }

Также стоит понять, что такое vtable.

-1
ответ дан 27 November 2019 в 02:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: