Вот код:
#include <iostream>
class BaseClass
{
public:
virtual void f() const;
};
void BaseClass::f() const
{
std::cout << "In base class function\n";
}
class DerivedClass: public BaseClass
{
public:
void f() const override;
};
void DerivedClass::f() const
{
std::cout << "In derived class function\n";
}
int main()
{
DerivedClass derived_object;
BaseClass base_object = derived_object;
BaseClass *base_object_ptr = &derived_object;
base_object.f();//вызовет f, определённый в суперклассе
base_object_ptr -> f();//вызовет f, определённый в наследнике
return 0;
}
В консоле сначала выведется In base class function, а затем In derived class function.
Виртуальные функции обеспечивают динамическое связывание (dynamic binding) и объектно-ориентированное программирование.
Решение о вызове виртуальной функции принимается на этапе выполнения программы, а не на этапе компиляции, как это происходит с невиртуальными функциями. Но такое поведение будет поддерживаться только при использовании ссылок или указателей.
В случае
base_object.f();
base_object не является ни ссылкой, ни указателем. Следовательно решение о вызове функции будет принято еще на этапе компиляции (как и наблюдаем - вызывается BaseClass::f).
При использовании указателя все работает как задумывалось - вызывается метод из DerivedClass, который перекрывает соответствующий виртуальнуй метод базового класса. Каким образом это сделано - детали реализации.
Второй момент - это конструирование BaseClass base_object = derived_object;
Что происходит в этом месте? На самом деле вызывается конструктор базового класса. Это копирующий конструктор, который сгенерирует компилятор (см. Правило трёх/пяти). Он имеет сигнатуру: BaseClass(const BaseClass&). Т.к. DerivedClass открыто наследует класс BaseClass, объект этого класса (DerivedClass) может быть преобразован к ссылке на BaseClass. Этим объясняется успех операции. Наглядно это можно увидеть, если объявить копирующий конструктор в BaseClass и сделать в нем вывод. Пример тут - https://wandbox.org/permlink/gITmHAUWMcksSeZP
Но такое явление чаще всего является неправильным, т.к. оно делает невозможным использование того самого полиморфизма, ради которого и создавались эти виртуальные методы. Это явление называют срезкой (slicing).
Одним предложением: Это главная особенность полимофизма времени выполнения. Но, а если подробно...
Производный класс может неявно преобразиться в базовый, так как базовый класс является его фундаментом (при преобразовании скопируется только та часть, что лежит в фундаменте, но в вашем вопросе это не имеет значения). Поэтому строкой:
BaseClass base_object = derived_object;
вы получаете вполне нормальный обьект типа BaseClass
и дальше в коде вызываете его метод.
Теперь рассмотрим указатель базового класса, который вполне может содержать адрес обекта производного:
BaseClass* base_object_ptr = &derived_object;
Каждый обьект полиморфного типа, неявно содержит указатель на виртуальную таблицу, поэтому вызывая base_object_ptr->f(); вызывется виртуальный метод обьекта derived_object, а не метод базового класса. Вот если в базовом классе убрать виртуальность функции f, тогда посредством указателья base_object_ptr вызвался бы метод базового класса, независимо от того, что он указывет на обьект производного(содержит его адрес).
Продвижение своими сайтами как стратегия роста и независимости