Понимание виртуальных функций в C++

Понимание виртуальных функций в C++

7 марта 2023 г.

Давайте рассмотрим идею виртуальных функций в C++. Это один из самых сложных предметов в C++. Мы разберем это понятие и изучим виртуальные функции с нуля. Мы рассмотрим многочисленные примеры, чтобы узнать, как они реализованы в C++. Итак, без дальнейших промедлений, давайте приступим к сегодняшней теме.

Наследование без виртуальных функций

Давайте посмотрим, чего можно добиться без использования виртуальных функций в C++. Рассмотрим следующий пример. Предположим, что у нас есть два разных класса для моделирования в нашем приложении, которые называются Person и Student. Между этими двумя классами существует естественное отношение наследования. Ученик тоже человек. Таким образом, мы можем получить следующую диаграмму UML.

Вот как будет выглядеть код для этих классов.

#include <string>
#include <iostream>

using namespace std;

class Person {
public:
    string f_name;
    string l_name;
    string get_id() {
        return f_name + " " + l_name;
    }
};

class Student : public Person {
    string roll_num;
    string get_id() {
        return roll_num + " " + f_name + " " + l_name;
    }
};

Давайте запустим несколько тестов, используя эти классы. Мы создадим объект типа Студент, а затем вызовем функцию get_id() для объекта Студент. После этого мы преобразуем объект студента в тип Person и попытаемся еще раз использовать функцию-член get_id().

int main() {
    Student student;
    student.f_name = "John";
    student.l_name = "Doe";
    student.roll_num = "2023ABC";
    student.get_id(); // "2023ABC John Doe"

    Person& person = student;
    person.get_id(); // "John Doe"
}

<цитата>

Результат кажется вам нелогичным?

Самый первый вызов get_id() выглядит нормально, поскольку он правильно вызывает функцию в классе Student. Но когда объект того же учащегося преобразуется в тип Person, он вызывает версию метода get_id() для Person. Что здесь происходит? Где полиморфизм?

Таким образом, C++ использует раннее связывание, чтобы решить, какое тело функции использовать. Во время компиляции компилятор никогда не может знать, каков истинный тип объекта person. Таким образом, он использует доступный ему тип информации и вызывает связанную с ним функцию. Отсюда и результат.

Но что, если мы хотим, чтобы вызывалась функция-член, связанная с фактическим типом объекта? Оказывается, этого нельзя добиться в C++ без использования виртуальных функций.

Введение в виртуальные функции

Давайте исправим нашу предыдущую программу, используя виртуальные функции. Единственное, что нам нужно сделать, это обновить метод get_id() в классе Person. Вот как выглядит обновленный код.

#include <string>
#include <iostream>

using namespace std;

class Person {
public:
    string f_name;
    string l_name;
    virtual string get_id() {
        return f_name + " " + l_name;
    }
};

class Student : public Person {
public:
    string roll_num;
    string get_id() {
        return roll_num + " " + f_name + " " + l_name;
    }
};

int main() {
    Student student;
    student.f_name = "John";
    student.l_name = "Doe";
    student.roll_num = "2023ABC";
    student.get_id(); // "2023ABC John Doe"

    Person& person = student;
    person.get_id(); // "2023ABC John Doe"
}

Простое добавление ключевого слова virtual перед именем функции в родительском классе сработало как по волшебству!

Но что же произошло на самом деле?

Когда мы помечаем функцию как виртуальную, это указывает компилятору ввести некоторую дополнительную логику для разрешения фактического вызова метода во время выполнения. Таким образом, когда мы помечаем функцию get_id() как виртуальную функцию, каждый раз, когда мы вызываем этот метод, дополнительный код, введенный компилятором, будет использоваться для вызова правильной реализации метода на основе фактического типа объекта. объект. Вот как мы используем виртуальные функции!!

n Как реализуются виртуальные функции?

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

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

Вот полезное описание того, как работают виртуальные указатели и виртуальные таблицы.

Следующий шаг…

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

Спасибо и поздравляю всех с завершением этого блога. Надеюсь, вам понравилось. Пожалуйста, оставьте комментарий, поставьте лайк и следите за новостями, чтобы узнать больше!


Также опубликовано здесь


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE