Ответ
Таблица виртуальных функций (vtable) — это внутренний механизм компилятора C++ для реализации динамического полиморфизма (позднего связывания). Для каждого класса, содержащего виртуальные функции, компилятор неявно создает массив указателей на эти функции. Каждый объект такого класса содержит скрытый указатель (vptr) на vtable своего класса.
Как это работает:
- Компилятор создает уникальную vtable для каждого полиморфного класса.
- В конструкторе объекта указатель vptr инициализируется адресом vtable соответствующего класса.
- При вызове виртуальной функции через указатель или ссылку на базовый класс, код:
- Через vptr находит vtable объекта.
- По фиксированному смещению в vtable находит адрес нужной функции.
- Вызывает эту функцию.
Пример:
#include <iostream>
class Base {
public:
virtual void func1() { std::cout << "Base::func1n"; }
virtual void func2() { std::cout << "Base::func2n"; }
virtual ~Base() {} // Виртуальный деструктор обязателен для полиморфных классов
};
class Derived : public Base {
public:
void func1() override { std::cout << "Derived::func1n"; } // Переопределение
// func2() не переопределена, в vtable Derived будет указатель на Base::func2
};
int main() {
Base* obj = new Derived(); // vptr объекта указывает на vtable класса Derived
obj->func1(); // Вызовется Derived::func1 через vtable
obj->func2(); // Вызовется Base::func2 через vtable
delete obj;
return 0;
}
Вывод:
Derived::func1
Base::func2
Накладные расходы:
- Память: Один дополнительный указатель (vptr) на каждый объект + память на саму vtable.
- Время: Двойное разыменование указателя при вызове (vptr -> vtable -> функция) и потеря возможностей инлайнинга.
Ответ 18+ 🔞
Давай я тебе на пальцах объясню, что это за хуйня — таблица виртуальных функций, или vtable. Представь, что компилятор C++ — это такой хитрая жопа, которая для полиморфизма втихую организует подпольный клуб по интересам.
Вот смотри. У тебя есть класс с виртуальными функциями. Компилятор смотрит на это и думает: «Ёпта, опять эти танцы с бубном». И втихаря для каждого такого класса создаёт секретную табличку — массив указателей на эти самые функции. А каждый объект этого класса получает в подарок скрытый указатель (vptr) на эту самую таблицу своего класса. Это как нашёптыватель в ухо — он всегда знает, кого позвать на танец.
Как эта магия, блядь, работает:
- Компилятор для каждого полиморфного класса пилит свою уникальную vtable. Это типа меню в ресторане, но меню из адресов функций.
- Когда рождается объект (вызывается конструктор), его личный указатель vptr настраивается ровно на меню его родного класса.
- А дальше самое интересное. Когда ты через указатель на базовый класс вызываешь виртуальную функцию, происходит подковёрная возня:
- Через этот самый vptr объект лезет в свою vtable.
- Там, по заранее известному смещению (типа «второе блюдо в меню»), выуживает адрес нужной тебе функции.
- И уже вызывает её. Всё, позднее связывание случилось, ты доволен.
Смотри на примере, чтобы вообще ни хуя не осталось непонятного:
#include <iostream>
class Base {
public:
virtual void func1() { std::cout << "Base::func1n"; }
virtual void func2() { std::cout << "Base::func2n"; }
virtual ~Base() {} // Виртуальный деструктор — святое дело, без него будет пиздец при удалении
};
class Derived : public Base {
public:
void func1() override { std::cout << "Derived::func1n"; } // Переопределили func1
// func2() не трогали, значит, в меню (vtable) Derived будет лежать адрес Base::func2
};
int main() {
Base* obj = new Derived(); // vptr этого объекта тычет в таблицу класса Derived
obj->func1(); // Полезет в таблицу Derived, достанет адрес Derived::func1, вызовет её
obj->func2(); // Полезет туда же, но достанет адрес Base::func2, потому что свою мы не делали
delete obj;
return 0;
}
На экране будет:
Derived::func1
Base::func2
А теперь про цену этой магии, потому что бесплатный сыр только в мышеловке:
- Память: Каждый объект таскает с собой лишний указатель (vptr). Плюс сама таблица где-то в памяти болтается. В сумме — овердохуища мелочи, но для миллионов объектов уже чувствительно.
- Скорость: Чтобы функцию вызвать, нужно два раза по указателям пройтись (vptr -> vtable -> функция). И самое печальное — компилятор не может такую функцию заинлайнить, то есть встроить прямо в код. Так что производительность немного сосёт лапу.
Вот и вся магия, ёпта. Удобно? Ебать как. Бесплатно? Хуй там, за всё надо платить.