Возвращаемый тип виртуальной функции С++

Возможно ли, чтобы унаследованный класс реализовал виртуальную функцию с другим типом возвращаемого значения (не используя шаблон в качестве возврата)?

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