ковариантный тип возврата

Почему этот код:

#include <iostream>
 
class Parent
{
public:
    virtual Parent* getThis() { std::cout << "called Parent::getThis()\n"; return this; }
    void printType() { std::cout << "returned a Parent\n"; }
};
 
class Child : public Parent
{
public:
    virtual Child* getThis() { std::cout << "called Child::getThis()\n";  return this; }
    void printType() { std::cout << "returned a Child\n"; }
};
 
int main()
{
    Child ch;
    Parent *p = &ch;
    ch.getThis()->printType(); 
    p->getThis()->printType();
}

Выдает следующий результат?

called Child::getThis()
returned a Child
called Child::getThis()
returned a Parent

Ведь в последней строчке должно выводиться returned a Child, так как p->getThis() возвращает указатель на Child


Ответы (3 шт):

Автор решения: Roman Ozhegov

В вашем случае getThis() - виртуальная функция, а printType нет, поэтому она не перекрывается.

Для ожидаемого Вами поведения необходимо ее тоже сделать виртуальной. Попробуйте так:

class Parent
{
public:
    virtual Parent* getThis() { std::cout << "called Parent::getThis()\n"; return this; }
    virtual void printType() { std::cout << "returned a Parent\n"; }
};

class Child : public Parent
{
public:
    virtual Child* getThis() { std::cout << "called Child::getThis()\n";  return this; }
    virtual void printType() { std::cout << "returned a Child\n"; }
};

int main()
{
    Child ch;
    Parent *p = &ch;
    ch.getThis()->printType();
    p->getThis()->printType();
}

Мой вывод:

called Child::getThis()
returned a Child
called Child::getThis()
returned a Child
→ Ссылка
Автор решения: AR Hovsepyan

Не важно, что указатель базового класса Parent* указывает на своего наследника. У каждого типа свои функции_члены, и через указатель на этот тип можно вызвать только его функции_члены. Исключением являются виртуальные функции, которые и обеспечивают полиморфизм времени выполнения, то есть то поведение, которые вы ожидаете. Но функция_член Parent::printType не является виртуальной(указатель на виртуальные функции, который неявно хранит объект, не может указывать на нее). Если вас запутывает то, что getThis возвращает Child*, то будьте внимательны:

Строчка p->getThis()->printType(); то же самое, что и

ch.getThis();
Parent* temp = &ch; 
temp->printType();

Потому что getThis в любом случаи должен вернуть Parent*, но благодаря полиморфизму и переопределению в производном классе, вместо этого получаете Child*, но он всеравно рассматривается как Parent*(при переопределении возвращаемый тип не меняется)

Но, по мне, вы думаете, что должно происходить что то типа:

std::cout << "called Child::getThis()\n";
Child* temp = &ch;
temp->printType();
→ Ссылка
Автор решения: αλεχολυτ

В первую очередь стоит понимать, что есть статический тип объекта, а есть динамический. Статические типы задаются на момент компиляции, динамические - определяются в процессе выполнения программы.

Например, указатель на базовый класс, объявленный как Parent* p; имеет статический тип Parent*. Динамический тип может как совпадать со статическим, так и указывать на производный тип, такой как Child*. Выбор функции-члена, вызываемой на том или ином указателе основывается на виртуальности функции. Если функция объявлена виртуальной, то используется динамический тип объекта, если же функция не является виртуальной, то используется статический тип объекта.

Приведу часть вашего кода здесь для удобства дальнейшего чтения:

Child ch;
Parent *p = &ch;
ch.getThis()->printType(); 
p->getThis()->printType();

При вызове ch.getThis()->printType(); первая часть, т.е. ch.getThis() имеет статический тип Child*, т.к. на этапе компиляции ясно, что используется функция производного класса Child. А значит последующий вызов ->printType() вызовет именно функцию производного класса (несмотря на то, что printType не является виртуальной) и выведет строку "returned a Child".

При вызове p->getThis()->printType(); первая часть, т.е p->getThis() имеет статический тип Parent*, т.к. тип, возвращаемый функцией Parent::getThis() - это Parent*. Динамический же тип в данном случае будет Child*, т.к. p указывает на объект производного класса и вызываемая функция getThis() является виртуальной. Но как уже было сказано выше, для невиртуальных функций динамический тип не играет роли, а важен именно тип статический. Поэтому итоговый вызов ->printType() выбирает именно функции из базового класса и выводится "returned a Parent".

→ Ссылка