Ответ Тони Д. правильно указывает на то, что компиляторам разрешено использовать анализ всей программы для замены виртуального вызова функции статическим вызовом уникальной возможной реализации функции; или скомпилировать obj->method()
в эквивалент
if (auto frobj = dynamic_cast<FrequentlyOccurringType>(obj)) {
frobj->FrequentlyOccurringType::method(); // static dispatch on hot path
} else {
obj->method(); // vtable dispatch on cold path
}
Карел Дризен и Урс Хельцле в 1996 году написали действительно увлекательный документ, в котором имитировали эффект идеальной оптимизации всей программы в типичных приложениях C ++: «Прямая стоимость вызовов виртуальных функций в C ++» . (PDF-файл доступен бесплатно, если вы Google для него.) К сожалению, они сравнивают только vtable dispatch с идеальной статической отправкой; они не сравнивали его с отправкой двоичного дерева.
Они указали , что на самом деле существует два типа виртуальных таблиц, когда вы говорите о языках (например, C ++), которые поддерживают множественное наследование. При множественном наследовании, когда вы вызываете виртуальный метод, который унаследован от второго базового класса, вам нужно «исправить» указатель объекта, чтобы он указывал на экземпляр второго базового класса. Это смещение исправления может быть сохранено как данные в vtable, или оно может быть сохранено как код в "thunk". (Подробнее см. В статье.)
Я полагаю, что все приличные компиляторы в наши дни используют thunks, но для того, чтобы проникновение на рынок достигло 100%, потребовалось 10 или 20 лет.