Это поведение, которое стандарт C ++ определяет для виртуальных функций: вызов версии самого доступного производного типа.
Конечно, для нормальных объектов самый производный тип - это тип самого объекта:
B b;
b.f1(); // of course calls B's version
Интересная часть, если у вас есть указатели или ссылки:
B b;
A& ar = b;
A* ap = &b;
// now both times, B's version will be called
ar.f1();
ap->f1();
То же самое происходит внутри f1, фактически вы делаете неявно:
this->f2(); // 'this' is a POINTER of type A* (or A const* in const functions).
Существует явление, когда этого не происходит (в приведенном ниже примере требуется конструктор копирования):
B b;
A a = b; // notice: not a pointer or reference!
A.f1(); // now calls A's version
В действительности здесь происходит только то, что копируется только часть A
из b
в a
и часть B
отбрасывается, поэтому a
на самом деле является истинным, не производным A
объектом.Это называется «разрезанием объектов» и является причиной того, что вы не можете использовать базовые объекты, например, в std::vector
для хранения полиморфных объектов, но вместо этого нужны указатели или ссылки.
Вернуться к виртуальным функциям:Если вас интересуют технические детали, это решается с помощью виртуальных таблиц функций, коротких таблиц.Имейте в виду, что это только де-факто стандарт, C ++ не требует реализации через vtables (и фактически, другие языки, поддерживающие полиморфизм / наследование, такие как Java или Python, также реализуют vtables).
Для каждогоВиртуальная функция в классе есть запись в соответствующей таблице.
Нормальные функции вызываются напрямую (т.е. выполняется безусловный переход к адресу функции).Для вызова виртуальных функций, напротив, сначала нужно найти адрес в виртуальной таблице, и только затем мы можем перейти к адресу, хранящемуся там.
Производные классы теперь копируют виртуальные таблицы своих базовых классов (поэтому изначально онисодержат те же адреса, что и таблицы базового класса), но замените соответствующие адреса, как только вы переопределите функцию.
Кстати: вы можете указать компилятору не использовать vtable, а явно вызвать определенныйвариант:
B b;
A& a = b;
a.A::f1(); // calls A's version inspite of being virtual,
// because you explicitly told so
b.A::f1(); // alike, works even on derived type