С точки зрения стандартного языка, сказать больше нечего, чем то, что вы, вероятно, уже знаете: если функция виртуальная, то ее вызов по любой ссылке или указателю базового класса вызовет самую производную фактическую функцию.
Вот и все.Все, что имеет значение, это поведение.
То, что вам действительно нужно, это то, как компиляторы реализуют это.Действительно, vtable является наиболее популярным способом реализации виртуальной диспетчеризации.По сути, это список указателей функций, который поддерживается для каждого класса, имеющего виртуальные функции.(Помните, что наследование от класса с виртуальными функциями автоматически делает эти функции виртуальными снова в производном классе.)
Однако способ, которым компилятор на самом деле вызывает функцию, меняется.
Если функция не виртуальная, она известна во время компиляции и отправляется статически в функцию-член типа статического класса, для которого она была вызвана.
Если функция виртуальная, но компилятор может доказать динамический тип базовой ссылки / указателя во время компиляции, он может выбрать непосредственный вызов функции соответствующего производного класса.
Если динамический тип не может быть выведен компилятором, диспетчеризация функции происходит во время выполнения путем поиска указателя функции фактической (наиболее производной) функции в виртуальной таблице.
Пример:
struct A {
void foo();
virtual void bar();
};
struct B : A {
void foo(); // hides A::foo() -- very bad style
void bar(); // automatically virtual!!
};
int main() {
B x;
A * a1 = &x; // pointer-to-base;
A * a2 = get_pointer();
a1->foo(); // static dispatch to A::foo() (non-virtual function)
a1->bar(); // dispatch to B::foo(), possibly resolved statically
a2->bar(); // dynamically dispatched to whatever the most derived class is
}