#cpp #указатели #наследование
Вот код: #includeclass 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.
Ответы
Ответ 1
Виртуальные функции обеспечивают динамическое связывание (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).Ответ 2
Одним предложением: Это главная особенность полимофизма времени выполнения. Но, а если подробно... Производный класс может неявно преобразиться в базовый, так как базовый класс является его фундаментом (при преобразовании скопируется только та часть, что лежит в фундаменте, но в вашем вопросе это не имеет значения). Поэтому строкой: BaseClass base_object = derived_object; вы получаете вполне нормальный обьект типа BaseClass и дальше в коде вызываете его метод. Теперь рассмотрим указатель базового класса, который вполне может содержать адрес обекта производного: BaseClass* base_object_ptr = &derived_object; Каждый обьект полиморфного типа, неявно содержит указатель на виртуальную таблицу, поэтому вызывая base_object_ptr->f(); вызывется виртуальный метод обьекта derived_object, а не метод базового класса. Вот если в базовом классе убрать виртуальность функции f, тогда посредством указателья base_object_ptr вызвался бы метод базового класса, независимо от того, что он указывет на обьект производного(содержит его адрес).
Комментариев нет:
Отправить комментарий