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

90
19 мая 2021, 20:50

Вот код:

#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.

Answer 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).

Answer 2

Одним предложением: Это главная особенность полимофизма времени выполнения. Но, а если подробно...

Производный класс может неявно преобразиться в базовый, так как базовый класс является его фундаментом (при преобразовании скопируется только та часть, что лежит в фундаменте, но в вашем вопросе это не имеет значения). Поэтому строкой:

BaseClass base_object = derived_object;

вы получаете вполне нормальный обьект типа BaseClass и дальше в коде вызываете его метод.

Теперь рассмотрим указатель базового класса, который вполне может содержать адрес обекта производного:

BaseClass* base_object_ptr = &derived_object;

Каждый обьект полиморфного типа, неявно содержит указатель на виртуальную таблицу, поэтому вызывая base_object_ptr->f(); вызывется виртуальный метод обьекта derived_object, а не метод базового класса. Вот если в базовом классе убрать виртуальность функции f, тогда посредством указателья base_object_ptr вызвался бы метод базового класса, независимо от того, что он указывет на обьект производного(содержит его адрес).

READ ALSO
Подмена маски номера телефона в зависимости от вводимой цифры

Подмена маски номера телефона в зависимости от вводимой цифры

Всем приветВообщем задачка нужно сделать маску как на https://www

112
overflow: hidden !important

overflow: hidden !important

На дисплеях с большим расширением я скрываю скролл по горизонтали

130
Ошибка сериализация бинарного файла

Ошибка сериализация бинарного файла

Ошибка возникает при вызове метода Save(), подскажите в чем причина? Пользователь создаеться без ошибок, а запись не проходит, хотя файл каким...

97