it-swarm.com.ru

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

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

72
Bob

В некоторых случаях, да, для производного класса допустимо переопределять виртуальную функцию с использованием другого возвращаемого типа, если возвращаемый тип ковариантный с исходным возвращаемым типом. Например, рассмотрим следующее:

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*, и операция четко определена.

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

76
templatetypedef

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

48
Rob Kennedy

Реализация производного класса виртуальной функции может иметь Ковариантный тип возвращаемого значения .

2
Nikolai Fetissov