Страницы

Поиск по вопросам

пятница, 9 ноября 2018 г.

В каких случаях использовать указатель на базовый класс, а в каких на наследник?

Не могли бы вы прокомментировать этот момент:
Используя виртуальные функции для обеспечения полиморфизма необходимо использовать указатель именно на базовый класс.
В каких случаях необходимо использовать указатель на класс наследник, а в каких на базовый класс?
Просто недавно наткнулся на такой пример, он корректно реализован?
#include using namespace std;
class Base { //Базовый класс public: int base_data; Base(int base_data);//Конструктор класса virtual void Virtual_method(); // Виртуальный метод void Nonvirtual_method(); // Не виртуальный метод virtual~Base(); //Виртуальный деструктор };
Base::Base(int base_data){ this->base_data = base_data; cout << "Конструктор базового" << endl; }
void Base::Virtual_method() { cout << "Виртуальный метод базового класса
"; }
void Base::Nonvirtual_method() { cout << "Не виртуальный метод базового класса
"; }
Base::~Base(){ cout << "Деструктор базового класса
"; }
class Derived : public Base {//Класс наследник public: double derived_data; Derived(double derived_data, int base_data); void Virtual_method(); // Переопределённый виртуальный метод базового класса void Nonvirtual_method(); //Не виртуальна функция ~Derived(); };
Derived::Derived(double derived_data, int base_data):Base(base_data){ this->derived_data = derived_data; cout << "Конструктор наследника" << endl; }
void Derived::Virtual_method() { cout << "Переопределённый виртуальный метод класса наследника
"; }
void Derived::Nonvirtual_method() { cout << "Не виртуальный метод класса наследника
"; }
Derived::~Derived(){ cout << "Деструтор класса наследника
"; }
int main() { setlocale(LC_ALL, "RUS"); Derived derived(2.0, 1);//Объявили объект класса наследника
Derived *pDerived = &derived;//Объявили указатель на класс наследник, присвоив ему ссылку на объект класса наследника Base *pBase = &derived;//Объявили указатель на базовый класс, присвоив ему ссылку на объект класса наследника
pBase->Virtual_method(); // Вызов виртуального метода pBase->Nonvirtual_method(); // Вызов не виртуального метода pDerived->Virtual_method(); // Вызов виртуального метода pDerived->Nonvirtual_method(); // Вызов не виртуального метода cout << "base_data = " << derived.base_data << "
derived_data = " << derived.derived_data << "
"; return 0; }


Ответ

Обычно, когда говорят о полиморфизме в программировании, то пытаются дать определение этого слова в терминах языков программирования.
Однако на мой взгляд более удачное определение полиморфизма дается в биологии. Только слово организм в этом определении следует заменить словом объект в терминах программирования.:)
Полиморфи́зм в биологии (от др.-греч. πολύμορφος — многообразный) — способность некоторых организмов существовать в состояниях с различной внутренней структурой или в разных внешних формах
В вашей демонстрационной программе определяется указатель типа Base *, то есть статический тип объектов, адресуемых этим указателем является тип Base
Base *pBase;
Когда такому указателю присваивается адрес объекта производного класса, то объект, к которому происходит обращение через этот указатель, рассматривается как объект типа Base. Фактически, вся информация о том, что этот объект на самом деле является производного класса, а не базового, теряется. так как статический тип указателя Base *
Однако благодаря наличию такого средства, как виртуальные функции, позволяет получать многообразие поведения адресуемого указателем объекта в зависимости от того, какой тип на самом деле имеет адресуемый объект. То есть через виртуальные функции имеется возможность различать реальный тип адресуемых объектов и обращаться к их уникальным свойствам. И это демонстрируется ваша программа на примере данных предложения
Base *pBase = &derived;//Объявили указатель на базовый класс, присвоив ему ссылку на объект класса наследника
pBase->Virtual_method(); // Вызов виртуального метода pBase->Nonvirtual_method(); // Вызов не виртуального метода
Как видно указатель типа Base * на самом деле адресует объект производного класса. Но если вызывать не виртуальный метод
pBase->Nonvirtual_method(); // Вызов не виртуального метода
то вся информация о производном классе недоступна, так как компилятор вызывает функции в соответствии со статическим типом объекта, адресуемого указателем, то есть в соответствии с типом Base. Компилятор при поиске имени функции, которую следует вызвать смотрит определение базового класса. Он ничего не знает о производных классах.
Ежели вы вызываете виртуальную функцию
pBase->Virtual_method(); // Вызов виртуального метода
то компилятор также при поиске имени вызываемой функции смотрит определение базового класса. Однако тут происходит некоторый фокус. Производный класс подменил определение виртуальной функции в базовом классе своим определением этой функции. Технически это делается следующим образом. Если класс объявляет виртуальную функцию, то он создает таблицу указателей на виртуальные функции, объявленные в своем определении. Производные же классы, которые переопределяют виртуальные функции подставляют в эту таблицу базового класса адреса своих определений функций. Поэтому динамически во время выполнения программы вызывается та функция, чей адрес находится в таблице адресов виртуальных функций.
Что касается вашего вопроса
В каких случаях необходимо использовать указатель на класс наследник, а в каких на базовый класс?
То когда вы объявляете указатель на класс наследник, то вы теряете полиморфизм, так как вы можете работать только с объектами класса наследника (я предполагаю, что наследник в свою очередь не наследуется другими классами). А когда вы хотите достичь поведение, соответствующее полиморфизму, то
1) базовый класс должен содержать виртуальные функции т 2) следует объявить указатель или ссылку, имеющую статический тип указателя или ссылки на объекты базового класса, а инициализировать их объектами производных классов.
И тогда вы получите
способность некоторых объектов (организмов) существовать в состояниях с различной внутренней структурой или в разных внешних формах
Примечание. Если при вызове виртуальной функции вы указываете ее квалифицированное имя, то "виртуальность" исчезает. Вызывается функция класса, определенная в этом же классе.
Рассмотрите пример
#include
struct Base { virtual ~Base() { } virtual void virtual_function() const { std::cout << "Base::virtual_function() is called" << std::endl; } };
struct Derived : Base { void virtual_function() const override { std::cout << "Derived::virtual_function() is called" << std::endl; } };
int main() { Derived d;
Base &rBase = d;;
rBase.virtual_function(); rBase.Base::virtual_function();
return 0; }
Ее вывод на консоль будет следующим
Derived::virtual_function() is called Base::virtual_function() is called
В первом случае вступает в игру полиморфизм. Вызывается переопределенная виртуальная функция производного класса Derived, хотя базовый тип ссылки - это класс Base
Во втором случае, когда используется квалифицированное имя, "виртуальность" фукнции пропадает, и вызывается реализация функции базового класса Base

Комментариев нет:

Отправить комментарий