Страницы

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

понедельник, 9 декабря 2019 г.

Почему от объекта вызывается один метод, а от указателя на объект - другой метод?

#cpp #указатели #наследование


Вот код:

#include 

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.
    


Ответы

Ответ 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 вызвался бы метод базового класса, независимо от того, что он указывет на обьект производного(содержит его адрес).

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

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