Назад к вопросам
Middle
143
questionbank

Как работает виртуальная функция в C++?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

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

Для реализации этого механизма компилятор добавляет к каждому объекту класса с виртуальными функциями скрытый указатель — vptr (virtual pointer). Этот указатель указывает на таблицу виртуальных функций — vtable (virtual table).

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

При вызове виртуальной функции через указатель или ссылку компилятор генерирует код, который:

  1. Получает указатель на vtable через vptr объекта.
  2. Находит в vtable указатель на нужную виртуальную функцию (по известному смещению).
  3. Вызывает функцию по полученному указателю.
class Base {
public:
    virtual void show() { // Виртуальная функция
        // ... реализация из базового класса
    }
};

class Derived : public Base {
public:
    void show() override { // Переопределение виртуальной функции
        // ... реализация из производного класса
    }
};

// Пример использования:
Base* ptr = new Derived(); // Указатель на базовый класс, указывающий на объект производного класса
ptr->show(); // Вызывается версия show() из класса Derived

delete ptr;

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

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

class Base {
public:
    virtual ~Base() { // Виртуальный деструктор
        // ... очистка ресурсов базового класса
    }
};

class Derived : public Base {
public:
    ~Derived() override { // Переопределение деструктора
        // ... очистка ресурсов производного класса
    }
};

// При удалении через указатель на Base будет вызван деструктор Derived, а затем Base
Base* obj = new Derived();
delete obj;

Если деструктор в Base не виртуальный, при удалении delete obj; будет вызван только деструктор Base, что приведет к утечкам памяти или другим проблемам, если Derived имеет свои ресурсы.