Тип возвращаемой виртуальной функции C++
Возможно ли для унаследованного класса реализовать виртуальную функцию с другим типом возврата (не используя шаблон в качестве возврата)?
3 ответа
В некоторых случаях, да, для производного класса допустимо переопределять виртуальную функцию с использованием другого возвращаемого типа, если возвращаемый тип является ковариантным по отношению к исходному возвращаемому типу. Например, рассмотрим следующее:
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*
и операция четко определена.
В более общем смысле тип возвращаемого значения функции никогда не считается частью ее сигнатуры. Вы можете переопределить функцию-член любым типом возвращаемого значения, если тип возвращаемого значения является ковариантным.
Да. Типы возвращаемых данных могут быть разными, если они ковариантны. Стандарт C++ описывает это так (§10.3/5):
Тип возврата переопределяемой функции должен быть либо идентичным типу возврата переопределенной функции, либо ковариантно классам функций. Если функция
D::f
переопределяет функциюB::f
тип возвращаемых функций ковариантен, если удовлетворяет следующим критериям:
- оба указатели на классы или ссылки на классы98)
- класс в возвращаемом типе
B::f
тот же класс, что и класс в типе возвращаемого значенияD::f
или, является однозначным прямым или косвенным базовым классом класса в типе возвратаD::f
и доступен вD
- оба указателя или ссылки имеют одинаковую квалификацию cv и тип класса в возвращаемом типе
D::f
имеет ту же квалификацию cv, что и квалификация cv или меньше, чем тип класса в возвращаемом типе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
обещает всегда возвращать Derived
указатель, то он всегда будет удовлетворять контракту, установленному классом предка, потому что любой Derived
указатель может быть неявно преобразован в Base
указатель. Таким образом, абоненты всегда получат то, что ожидают.
Помимо возможности изменения типа возвращаемого значения, некоторые языки также допускают изменение типов параметров переопределяющей функции. Когда они делают это, они обычно должны быть противоположными. То есть если B::f
принимает Derived*
, затем D::f
будет разрешено принять Base*
, Потомкам разрешено быть более свободными в том, что они примут, и строже в том, что они возвращают. C++ не допускает контравариантности типов параметров. Если вы меняете типы параметров, C++ считает это совершенно новой функцией, поэтому вы начинаете перегружаться и прятаться. Для получения дополнительной информации по этой теме см. Ковариация и контравариантность (информатика) в Википедии.
Реализация производного класса виртуальной функции может иметь Covariant Return Type.