Тип возвращаемой виртуальной функции C ++ - PullRequest
72 голосов
/ 12 января 2011

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

Ответы [ 3 ]

76 голосов
/ 12 января 2011

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

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

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

48 голосов
/ 12 января 2011

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

2 голосов
/ 12 января 2011

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

...