Возможно ли, чтобы унаследованный класс реализовал виртуальную функцию с другим типом возвращаемого значения (не используя шаблон в качестве возврата)?
Возвращаемый тип виртуальной функции С++
Ответ 1
В некоторых случаях, да, законным для производного класса является переопределение виртуальной функции с использованием другого типа возвращаемого значения, если тип возвращаемого значения является ковариантным с исходным типом возвращаемого значения. Например, рассмотрим следующее:
class Base {
public:
virtual ~Base() {}
virtual Base* clone() const = 0;
};
class Derived: public Base {
public:
virtual Derived* clone() const {
return new Derived(*this);
}
};
Здесь Base
определяет чистую виртуальную функцию с именем clone
, которая возвращает Base *
. В производной реализации эта виртуальная функция переопределяется с использованием типа возврата Derived *
. Хотя тип возврата не совпадает с базовым, это абсолютно безопасно, потому что в любое время вы пишете
Base* ptr = /* ... */
Base* clone = ptr->clone();
Вызов clone()
всегда будет возвращать указатель на объект Base
, так как даже если он возвращает Derived*
, этот указатель неявно конвертируется в Base*
, и операция корректно определена.
В общем случае возвращаемый тип функции никогда не считается частью его подписи. Вы можете переопределить функцию-член с любым типом возвращаемого значения, если тип возвращаемого значения является ковариантным.
Ответ 2
Да. Типы возвращаемых данных могут быть разными, если они covariant. Стандарт С++ описывает это следующим образом (§10.3/5):
Возвращаемый тип переопределяющей функции должен быть либо идентичен возвращаемому типу переопределенной функции, либо ковариант с классами функций. Если функция
D::f
переопределяет функциюB::f
, возвращаемый тип функций ковариант, если они удовлетворяют следующим критериям:
- оба являются указателями на классы или ссылки на классы 98)
- класс возвращаемого типа
B::f
является тем же классом, что и класс возвращаемого типаD::f
или, является однозначным прямым или косвенным базовым классом класса в возвращаемом типеD::f
и доступен вD
- оба указателя или ссылки имеют одинаковую cv-квалификацию, а тип класса в возвращаемом типе
D::f
имеет ту же самую cv-квалификацию, что или менее cv-qualification, чем тип класса в возвращаемом типеB::f
.
Сноска 98 указывает, что "многоуровневые указатели на классы или ссылки на многоуровневые указатели на классы не допускаются".
Короче говоря, если D
является подтипом B
, то возвращаемый тип функции в D
должен быть подтипом возвращаемого типа функции в B
. Самый распространенный пример - когда типы возврата сами основаны на D
и B
, но они не обязательно должны быть. Рассмотрим это, где мы имеем две отдельные иерархии типов:
struct Base { /* ... */ };
struct Derived: public Base { /* ... */ };
struct B {
virtual Base* func() { return new Base; }
virtual ~B() { }
};
struct D: public B {
Derived* func() { return new Derived; }
};
int main() {
B* b = new D;
Base* base = b->func();
delete base;
delete b;
}
Причина этого в том, что любой вызывающий объект func
ожидает указатель Base
. Любой указатель Base
будет делать. Итак, если D::func
promises всегда возвращает указатель Derived
, то он всегда будет удовлетворять контракту, установленному классом предка, потому что любой указатель Derived
может быть неявно преобразован в указатель Base
. Таким образом, абоненты всегда получат то, что они ожидают.
В дополнение к тому, что тип возвращаемого значения может меняться, некоторые языки также позволяют изменять типы параметров функции переопределения. Когда они это делают, они обычно должны быть контравариантными. То есть, если B::f
принимает a Derived*
, тогда D::f
будет разрешено принимать Base*
. Потомкам разрешено быть свободными в том, что они будут принимать, и более строгими в том, что они возвращают. С++ не допускает контравариантность параметрического типа. Если вы измените типы параметров, С++ считает это совершенно новой функцией, поэтому вы начинаете перегружаться и скрываться. Подробнее об этой теме см. Ковариация и контравариантность (информатика) в Википедии.
Ответ 3
Реализация виртуальной функции производного класса может иметь Ковариантный тип возврата.