86 votes

Type de retour d'une 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 un modèle comme retour) ?

91voto

templatetypedef Points 129554

Dans certains cas, oui, il est légal pour une classe dérivée de surcharger une fonction virtuelle en utilisant un type de retour différent, tant que le type de retour est covariant avec le type de retour original. Prenons l'exemple suivant :

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 surchargée en utilisant un type de retour de Derived * . Bien que le type de retour ne soit pas le même que dans la base, ceci est parfaitement sûr car à chaque fois que vous écrivez

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

L'appel à clone() renvoie toujours un pointeur sur un Base car même s'il renvoie un objet 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 par n'importe quel type de retour, à condition que celui-ci soit covariant.

9 votes

Cette phrase "Vous pouvez surcharger une fonction membre avec n'importe quel type de retour" n'est pas correcte. Vous pouvez remplacer une fonction membre tant que le type de retour est identique ou covariant (ce que vous avez expliqué), point final. Il n'y a pas de cas plus général.

2 votes

@bronekk- Le reste de la phrase que vous avez citée indique que le nouveau type de retour doit être utilisable partout où le type original le serait ; c'est-à-dire que le nouveau type est covariant avec l'original.

0 votes

Le reste de la phrase n'est pas correct ; imaginez que vous remplaciez Base* con long y Derived* con int (ou l'inverse, peu importe). Cela ne fonctionnera pas.

56voto

Rob Kennedy Points 107381

Oui. Les types de retour peuvent être différents tant qu'ils sont covariant . La norme C++ le décrit comme suit (§10.3/5) :

Le type de retour d'une fonction de surcharge est soit identique au type de retour de la fonction de surcharge, 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'il satisfait aux critères suivants :

  • les deux sont des pointeurs vers des classes ou des références à des classes 98)
  • la classe dans le type de retour de B::f est la même classe que la classe du type de retour de 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 en D
  • les deux pointeurs ou références ont la même qualification cv et le type de classe dans le type de retour de D::f a la même qualification cv ou une qualification cv inférieure au type de classe dans le type de retour de B::f .

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

En bref, si D est un sous-type de B alors le type de retour de la fonction dans D doit être un sous-type du type de retour de la fonction en B . L'exemple le plus courant est celui où les types de retour sont eux-mêmes basés sur des D y B mais ils ne sont pas obligés de l'être. Prenons l'exemple suivant, 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 Base pointeur. Tous les Base fera l'affaire. Ainsi, si D::func promet de toujours renvoyer un Derived il satisfera toujours au contrat établi par la classe ancêtre, car tout pointeur Derived peut être implicitement converti en un pointeur Base pointeur. Ainsi, les appelants obtiendront toujours ce qu'ils attendent.


En plus de permettre la variation du type de retour, certains langages autorisent l'utilisation de la fonction types de paramètres de la fonction prioritaire. Lorsqu'ils le font, ils ont généralement besoin d'être contravariant . En d'autres termes, si B::f accepte un Derived* entonces D::f serait autorisé à accepter un Base* . Les descendants sont autorisés à plus lâche dans ce qu'ils acceptent, et plus strict dans ce qu'ils renvoient. Le C++ n'autorise pas la contravariance de type paramètre. Si vous changez les types de paramètres, le C++ considère qu'il s'agit d'une toute nouvelle fonction, ce qui entraîne des problèmes de surcharge et de dissimulation. Pour plus d'informations sur ce sujet, voir Covariance et contravariance (informatique) dans Wikipédia.

2 votes

S'agit-il d'une fonctionnalité réelle ou d'un effet secondaire dû au fait que le type de retour n'est pas utilisé dans la résolution ?

1 votes

@Martin, il s'agit bien d'une fonctionnalité. Je suis presque sûr que la résolution des surcharges n'a rien à voir avec cela. Le type de retour es utilisé si vous remplacez une fonction.

3voto

Nikolai N Fetissov Points 52093

La mise en œuvre de la fonction virtuelle par une classe dérivée peut avoir une fonction Type de retour covariant .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X