Да. Типы возврата могут быть разными, если они ковариантны . Стандарт 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 ++ считает это совершенно новой функцией, поэтому вы начинаете перегружаться и прятаться. Подробнее об этой теме см. Ковариация и контравариантность (информатика) в Википедии.