Виртуальные функции имеют очень незначительное снижение производительности по сравнению с прямыми вызовами. На низком уровне вы в основном смотрите на поиск массива, чтобы получить указатель на функцию, а затем вызов через указатель на функцию. Современные процессоры могут даже достаточно хорошо прогнозировать косвенные вызовы функций в своих предикторах ветвления, поэтому они, как правило, не будут слишком вредить современным конвейерам ЦП. На уровне сборки вызов виртуальной функции переводится в нечто вроде следующего, где I
- произвольное непосредственное значение.
MOV EAX, [EBP + I] ; Move pointer to class instance into register
MOV EBX, [EAX] ; Move vtbl pointer into register.
CALL [EBX + I] ; Call function
Vs. следующее для прямого вызова функции:
CALL I ; Call function directly
Реальные издержки заключаются в том, что виртуальные функции по большей части не могут быть встроенными. (Они могут быть на языках JIT, если виртуальная машина понимает, что они всегда идут по одному и тому же адресу.) Помимо ускорения, которое вы получаете благодаря самому встраиванию, встраивание обеспечивает несколько других оптимизаций, таких как постоянное свертывание, потому что вызывающий может знать, как вызываемый работает внутри. Для функций, которые достаточно велики, чтобы их нельзя было встроить, снижение производительности, скорее всего, будет незначительным. Для очень маленьких функций, которые могут быть встроены, вам нужно быть осторожным с виртуальными функциями.
Редактировать: еще одна вещь, которую нужно иметь в виду, это то, что все программы требуют управления потоком, и это никогда не бывает бесплатным. Что заменит вашу виртуальную функцию? Смена заявления? Серия заявлений? Это все еще ветви, которые могут быть непредсказуемыми. Кроме того, при наличии N-way ветви, серия операторов if найдет правильный путь в O (N), а виртуальная функция найдет его в O (1). Оператор switch может быть O (N) или O (1) в зависимости от того, оптимизирован ли он для таблицы переходов.