Хороший ответ уже есть, но я попробую немного более простой (хотя и более длинный):
Подумайте о не виртуальном методе вида
class A
{
public:
int fn(int arg1);
};
как эквивалент свободной функции вида:
int fn(A* me, int arg1); // overload A
, где me
соответствует указателю this
внутри версии метода.
Если у вас теперь есть подкласс:
class B : public A
{
public:
int fn(int arg1);
};
это эквивалентно свободной функции, подобной этой:
int fn(B* me, int arg1); // overload B
Обратите внимание, что первый аргумент имеет тип, отличный от свободной функции, которую мы объявили ранее - функция перегружена на тип первого аргумента.
Если у вас теперь есть код, вызывающий fn()
, он выберет перегрузку на основе статического типа (тип времени компиляции) первого аргумента:
A* p;
B* q;
// ...
// assign valid pointer values to p and q
// ...
int a = fn(p, 0); // will call overload A
int b = fn(q, 0); // will call overload B
Компилятор может и будет определять функцию, вызываемую во время компиляции в каждом случае, и может генерировать код сборки с фиксированным адресом функции или смещением адреса. Концепция виртуальной таблицы времени выполнения здесь не имеет смысла.
Теперь, когда я сказал думать о версии метода как эквивалентной версии свободной функции, вы обнаружите, что на уровне ассемблера они эквивалентны . Единственным отличием будет так называемое искаженное имя, которое кодирует тип в имени скомпилированной функции и различает перегруженные функции. Тот факт, что вы вызываете методы через p->fn(0)
, то есть с первым аргументом перед , имя метода - чистый синтаксический сахар - вы не на самом деле разыменование указателя p
в примере, даже если оно выглядит так. Вы просто передаете p
в качестве неявного this
аргумента. Итак, чтобы продолжить приведенный выше пример,
p->fn(0); // will always call A::fn()
q->fn(0); // will always call B::fn()
, поскольку fn
- не виртуальный метод, означает, что компилятор отправляет тип this
указателя static , что он может делать во время компиляции.
Хотя виртуальные функции используют тот же самый синтаксис вызова , что и не виртуальные функции-члены, вы фактически разыменовываете указатель объекта; в частности, вы разыменовываете указатель на виртуальную таблицу класса объекта.