web-dev-qa-db-fra.com

Type de retour de fonction virtuelle C ++

Est-il possible pour une classe héritée d'implémenter une fonction virtuelle avec un type de retour différent (sans utiliser de modèle comme retour)?

72
Bob

Dans certains cas, oui, il est légal pour une classe dérivée de remplacer une fonction virtuelle en utilisant un type de retour différent tant que le type de retour est covariant avec le type de retour d'origine. Par exemple, tenez compte des éléments suivants:

class Base {
public:
    virtual ~Base() {}

    virtual Base* clone() const = 0;
};

class Derived: public Base {
public:
    virtual Derived* clone() const {
        return new Derived(*this);
    }
};

Ici, Base définit une fonction virtuelle pure appelée clone qui renvoie un Base *. Dans l'implémentation dérivée, cette fonction virtuelle est remplacée à l'aide d'un type de retour de Derived *. Bien que le type de retour ne soit pas le même que dans la base, cela est parfaitement sûr car à chaque fois que vous écririez

Base* ptr = /* ... */
Base* clone = ptr->clone();

L'appel à clone() renverra toujours un pointeur vers un objet Base, car même s'il renvoie un Derived*, Ce pointeur est implicitement convertible en un Base* et l'opération est bien définie.

Plus généralement, le type de retour d'une fonction n'est jamais considéré comme faisant partie de sa signature. Vous pouvez remplacer une fonction membre avec n'importe quel type de retour tant que le type de retour est covariant.

76
templatetypedef

Oui. Les types de retour peuvent être différents tant qu'ils sont covariants . Le standard C++ le décrit comme ceci (§10.3/5):

Le type de retour d'une fonction prioritaire doit être soit identique au type de retour de la fonction substituée, soit covariant avec les classes des fonctions. Si une fonction D::f remplace une fonction B::f, le type de retour des fonctions est covariant s'ils satisfont aux critères suivants:

  • les deux sont des pointeurs vers des classes ou des références à des classes98)
  • la classe dans le type de retour de B::f est la même classe que la classe du type de retour D::f ou, est une classe de base directe ou indirecte non ambiguë de la classe dans le type de retour de D::f et est accessible dans D
  • les deux pointeurs ou références ont la même qualification cv et le type de classe dans le type de retour D::f a la même qualification cv que la qualification cv ou moins que le type de classe dans le type de retour de B::f.

La note de bas de page 98 souligne que "les pointeurs à plusieurs niveaux vers les classes ou les références à des pointeurs à plusieurs niveaux vers les classes ne sont pas autorisés".

En bref, si D est un sous-type de B, le type de retour de la fonction dans D doit être un sous-type du type de retour de la fonction dans B. L'exemple le plus courant est lorsque les types de retour sont eux-mêmes basés sur D et B, mais ils ne doivent pas l'être. Considérez ceci, où nous avons deux hiérarchies de types distinctes:

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;
}

La raison pour laquelle cela fonctionne est que tout appelant de func attend un pointeur Base. Tout pointeur Base fera l'affaire. Donc si D::func promet de toujours renvoyer un pointeur Derived, alors il satisfera toujours le contrat défini par la classe ancêtre car tout pointeur Derived peut être implicitement converti en un pointeur Base . Ainsi, les appelants obtiendront toujours ce qu'ils attendent.


En plus de permettre au type de retour de varier, certaines langues permettent également aux types de paramètres de la fonction prioritaire de varier. Quand ils le font, ils doivent généralement être contravariants . Autrement dit, si B::f accepte un Derived*, puis D::f serait autorisé à accepter un Base*. Les descendants peuvent être plus lâches dans ce qu'ils accepteront et plus stricts dans ce qu'ils reviennent. C++ n'autorise pas la contravariance de type paramètre. Si vous modifiez les types de paramètres, C++ le considère comme une toute nouvelle fonction, vous commencez donc à vous surcharger et à vous cacher. Pour plus d'informations sur ce sujet, voir Covariance et contravariance (informatique) dans Wikipedia.

48
Rob Kennedy

Une implémentation de classe dérivée de la fonction virtuelle peut avoir un type de retour covariant .

2
Nikolai Fetissov