← Назад к вопросам
Что такое vtable и vptr? Как работает механизм виртуальных функций?
1.7 Middle🔥 151 комментариев
#ООП и проектирование#Структуры данных и алгоритмы#Язык C++
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
vtable и vptr: механизм виртуальных функций
vtable (virtual method table) и vptr (virtual pointer) — это низкоуровневые механизмы реализации полиморфизма.
Что такое vtable и vptr
- vtable — таблица указателей на виртуальные функции класса
- vptr — скрытый указатель в каждом объекте, указывающий на vtable
- Каждый класс с виртуальными функциями имеет свою vtable
Как работает механизм
#include <iostream>
class Base {
public:
virtual void method1() { std::cout << "Base::method1" << std::endl; }
virtual void method2() { std::cout << "Base::method2" << std::endl; }
virtual ~Base() {}
};
class Derived : public Base {
public:
void method1() override { std::cout << "Derived::method1" << std::endl; }
void method2() override { std::cout << "Derived::method2" << std::endl; }
};
int main() {
Derived obj;
Base* ptr = &obj;
ptr->method1(); // Вызовет Derived::method1
ptr->method2(); // Вызовет Derived::method2
return 0;
}
Внутреннее устройство:
- Создание объекта Derived: объект содержит скрытый vptr
- Инициализация vptr: конструктор устанавливает vptr на vtable Derived
- Вызов виртуальной функции:
- Получить vptr из объекта
- Найти адрес функции в vtable (по индексу)
- Вызвать функцию
Размер объекта с vptr
#include <iostream>
class WithoutVirtual {
int x; // 4 байта
};
class WithVirtual {
public:
virtual void foo() {}
int x; // 4 байта
};
int main() {
std::cout << "Without virtual: " << sizeof(WithoutVirtual) << std::endl;
// 4 байта (только int)
std::cout << "With virtual: " << sizeof(WithVirtual) << std::endl;
// 16 байт (8 байт vptr + 4 байта int + padding)
return 0;
}
Множественное наследование и vtable
#include <iostream>
class A {
public:
virtual void methodA() { std::cout << "A" << std::endl; }
virtual ~A() {}
};
class B {
public:
virtual void methodB() { std::cout << "B" << std::endl; }
virtual ~B() {}
};
class C : public A, public B {
public:
void methodA() override { std::cout << "C::A" << std::endl; }
void methodB() override { std::cout << "C::B" << std::endl; }
};
int main() {
C obj;
A* ptrA = &obj;
B* ptrB = &obj;
// Оба указателя указывают на один объект,
// но на разные vtable!
ptrA->methodA(); // C::A
ptrB->methodB(); // C::B
return 0;
}
Объект содержит несколько vptr (по одному для каждого базового класса).
Трассировка вызова
class Animal {
public:
virtual void speak() { std::cout << "Animal" << std::endl; }
virtual ~Animal() {}
};
class Dog : public Animal {
public:
void speak() override { std::cout << "Dog" << std::endl; }
};
class Cat : public Animal {
public:
void speak() override { std::cout << "Cat" << std::endl; }
};
int main() {
Dog dog;
Cat cat;
Animal* animals[] = { &dog, &cat };
for (int i = 0; i < 2; ++i) {
// 1. Получить vptr из animals[i]
// 2. Получить адрес speak() из vtable
// 3. Вызвать speak()
animals[i]->speak();
}
return 0;
}
Как компилятор генерирует код
// На C++
ptr->method1();
// Компилятор генерирует примерно:
mov rax, qword ptr [ptr] // Получить адрес объекта
mov rbx, qword ptr [rax] // Получить vptr
mov rcx, qword ptr [rbx] // Получить функцию из vtable
call rcx // Вызвать функцию
Отключение виртуального вызова
class Base {
public:
virtual void func() { std::cout << "Base" << std::endl; }
};
class Derived : public Base {
public:
void func() override { std::cout << "Derived" << std::endl; }
};
int main() {
Derived obj;
Base* ptr = &obj;
ptr->func(); // Через vtable -> Derived
obj.Derived::func(); // Прямой вызов -> Derived
obj.Base::func(); // Прямой вызов -> Base
return 0;
}
Производительность
Стоимость виртуального вызова:
- Дополнительное разыменование указателя (2-3 такта)
- Нарушение предсказания ветвлений процессора
- Отсутствие инлайнинга оптимизатором
Ключевые моменты
- vptr добавляется только к классам с виртуальными функциями
- Один vptr на объект (несколько при множественном наследовании)
- vtable — глобальна, общая для всех объектов класса
- Выбор функции происходит в runtime, а не в compile-time
- Производительность немного хуже, чем прямые вызовы
Итог: vtable/vptr обеспечивают полиморфизм путём косвенного вызова функций через таблицы указателей, что позволяет одному указателю вызывать разные реализации в зависимости от типа объекта.