# [C++] Polymorphisme et const (résolu)

## Magic Banana

Tout d'abord, je vous prie de m'excuser pour ce sujet qui n'a pas grand chose à voir avec Gentoo (remarquez, pas plus que tous les autres fils qui traitent de problèmes spécifiques à certains logiciels s'exécutant sur Gentoo...).

J'ai passé, aujourd'hui plusieurs heures sur un problème de polymorphisme (fonctions "virtual"). Voici le code problématique à peine simplifié :

```
#include <iostream>

using namespace std;

class Base {

public:

  virtual ~Base() {}

  virtual int f() const { return 0; }

};

class Derived : public Base {

  int i;

public:

  Derived() : i(0) {}

  int f() { return ++i; }

};

int main() {

  Base* d = new Derived();

  cout << d->f() << endl;

  delete d;

}
```

Évidemment, le main n'est ici pas très intéressant puisqu'il est évident que d pointe sur une instance de Derived. Dans le cas réel, cela dépend d'une option passée par l'utilisateur. De plus, l'équivalent de la fonction f est appelé plus d'une fois sur un même objet.

Bref, g++ 4.4.3 compile le code ci-dessus sans le moindre avertissement (en utilisant les options "-Wall -Wextra -Weffc++ -std=c++98 -pedantic"). À l'exécution... "0" ! Le code de Base::f est exécuté à la place de celui de Derived::f. Pourtant Base::f est bien déclarée virtual et Derived::f a même prototype. En fait, la petite différence qui crée le soucis est le "const" de Base::f. En l'enlevant, le polymorphisme fonctionne, c'est à dire l'exécution imprime "1".

Ce comportement est-il vraiment conforme au standard ? Si oui, ne pensez-vous pas que ce comportement mérite un avertissement ?Last edited by Magic Banana on Thu Jan 20, 2011 3:10 am; edited 1 time in total

----------

## k-root

+1 pour le warning

mais un virtual int f() const , cela n'a pas trop de sens de vouloir fair un override derriere .. et c'est pas beau

----------

## Poussin

 *k-root wrote:*   

> +1 pour le warning
> 
> mais un virtual int f() const , cela n'a pas trop de sens de vouloir fair un override derriere .. et c'est pas beau

 

Ca c'est la """magie""" du C++, que cela aie du sens ou pas, on peut le faire!

----------

## k-root

the philosophy of C++ is to give the programmer as much latitude and as many options as possible, even if it increases the likelihood of making a mistake.

To actually determine if a method named X actually overrides another method X in a parent class is not a trivial problem in C++

----------

## netfab

Pour moi ceci est un comportement normal. f() const et f() n'ont n'ont pas les mêmes signatures, donc aucun lien entre ces 2 fonctions lors de l'héritage, donc pas de warning.

Après, dans ce cas précis, pourquoi le compilateur choisit d'appeler la fonction f() const de la classe de base plutôt que la fonction f() de la classe dérivée, à mon avis une histoire de static typing/dynamic binding.

 *Quote:*   

> 
> 
> mais un virtual int f() const , cela n'a pas trop de sens de vouloir fair un override derriere .. 
> 
> 

 

sauf si c'est pour renvoyer une autre valeur que 0 dans la classe dérivée, l'important étant que l'instance de la classe ne soit pas modifée (ce qui n'est pas le cas dans le code au dessus).

----------

## k-root

 *netfab wrote:*   

> sauf si c'est pour renvoyer une autre valeur que 0 dans la classe dérivée, l'important étant que l'instance de la classe ne soit pas modifée (ce qui n'est pas le cas dans le code au dessus).

 

si c'est pour renvoyer 0 dans les class derivées alors pourquoi la declarer virtual ? comment le compilateur change le pointeur de f si on lui indique que c'est const  :  avec virtual  f() const ce n'est pas overridable.

----------

## Magic Banana

Désolé de ne pas réagir plus vite (un contre-temps).

 *k-root wrote:*   

> mais un virtual int f() const , cela n'a pas trop de sens de vouloir fair un override derriere .. et c'est pas beau

 

Sauf à dire que le polymorphisme "c'est pas beau" (et je ne te suivrais pas sur ce point), je ne comprends pas pourquoi un tel code n'a pas de sens. Prenons l'exemple classique d'une hiérarchie d'animaux et d'une méthode "crie" (qui n'a pas même besoin de renvoyer quoi que ce soit). Cette méthode est const dans la classe de base ("Animal") et ne fait rien. La plupart des classes dérivées soit (a) ne surchargeront pas la méthode (car les animaux de cette espèce crie comme ceux de de l'espèce mère) soit (b) joueront un fichier son spécifique... mais peut-être que, pour une espèce particulière, on va aussi vouloir mettre à jour une variable de classe (disons l'heure du dernier cri) parce que l'information qu'elle porte a une importance quelconque sur le comportement futur de l'animal. Pour cette dernière classe on a plus de "const"... et on a, d'après ma découverte récente, besoin de supprimer les const de toutes les classes mères ! L'extensibilité est censée être un intérêt majeur du polymorphisme mais là...

 *netfab wrote:*   

> Pour moi ceci est un comportement normal. f() const et f() n'ont n'ont pas les mêmes signatures, donc aucun lien entre ces 2 fonctions lors de l'héritage, donc pas de warning.
> 
> Après, dans ce cas précis, pourquoi le compilateur choisit d'appeler la fonction f() const de la classe de base plutôt que la fonction f() de la classe dérivée, à mon avis une histoire de static typing/dynamic binding.

 

Comme Base::f est virtual, le lien devrait être dynamique. C'est d'ailleurs ce qu'explique la réponse à la question 20.3  sur la FAQ que tu pointes. Sauf erreur de ma part, int Base::f() et int Derived::f() const ont les mêmes signatures. Dans la définition de cette signature ne rentrent en compte "que" le nom de la méthode et la liste des types des arguments. C'est bien pour cela que ce comportement me surprend et que je vais même jusqu'à me demander si tout cela est conforme au standard.

EDIT : C'est bien ma définition personnelle de la signature qui est erronée :

 *The C++ Standard wrote:*   

> 1.3.10 signature [defns.signature]
> 
> the information about a function that participates in overload resolution (13.3): the types of its parameters and, if the function is a class member, the cv- qualifiers (if any) on the function itself and the class in which the member function is declared.2) The signature of a function template specialization includes the types of its template arguments (14.5.5.1).

 

Derived::f n'a donc aucune raison de surcharger Base::f et ce fil est résolu... sauf que, du coup, cela doit signifier que l'on peut avoir, dans une même classe, deux méthodes dont la signature ne diffère que par le qualifieur "const". Comment le compilateur choisit-il la méthode ? Il ne peut pas toujours deviner si l'on souhaite ou non que l'appel modifie l'objet. Lorsqu'il est certain, c'est en faveur de la méthode "const" (par exemple l'objet a été déclaré comme tel avant l'appel). Du coup, pour être déterministe, je suppose qu'il choisit la méthode non "const" dans tous les autres cas. À vérifier...

 *k-root wrote:*   

> comment le compilateur change le pointeur de f si on lui indique que c'est const : avec virtual f() const ce n'est pas overridable.

 

Non. Comme l'explique netfab, ce const signifie que la méthode ne modifie aucune variable de classe. C'est bien le cas dans Base. Ce n'est pas le cas dans Derived. Ajouter const à Derived::f n'est donc pas possible (le compilateur renverrait une erreur).

----------

## k-root

faites des interfaces ...

----------

## netfab

 *Quote:*   

> 
> 
> La plupart des classes dérivées soit (a) ne surchargeront pas la méthode (car les animaux de cette espèce crie comme ceux de de l'espèce mère) soit (b) joueront un fichier son spécifique... mais peut-être que, pour une espèce particulière, on va aussi vouloir mettre à jour une variable de classe (disons l'heure du dernier cri) parce que l'information qu'elle porte a une importance quelconque sur le comportement futur de l'animal. Pour cette dernière classe on a plus de "const"... et on a, d'après ma découverte récente, besoin de supprimer les const de toutes les classes mères ! L'extensibilité est censée être un intérêt majeur du polymorphisme mais là...
> 
> 

 

Tu peux déclarer ta variable mutable.

```

class Derived : public Base {

  mutable int i;

public:

  Derived() : i(0) {}

  int f() const { return ++i; }

};

```

 *Quote:*   

> 
> 
> sauf que, du coup, cela doit signifier que l'on peut avoir, dans une même classe, deux méthodes dont la signature ne diffère que par le qualifieur "const". Comment le compilateur choisit-il la méthode ?
> 
> 

 

Tout dépend de l'appel :

```

class Derived {

  int i;

public:

  Derived() : i(0) {}

  int f() const { return 7; }

  int f() { return ++i; }

};

int main() {

  Derived* d = new Derived();

  const Derived& ref = *d;

  cout << d->f() << endl;

  cout << ref.f() << endl;

  delete d;

}

```

 *Quote:*   

> 
> 
> $ ./a.out 
> 
> 1
> ...

 

 *k-root wrote:*   

>  *netfab wrote:*   sauf si c'est pour renvoyer une autre valeur que 0 dans la classe dérivée, l'important étant que l'instance de la classe ne soit pas modifée (ce qui n'est pas le cas dans le code au dessus). 
> 
> si c'est pour renvoyer 0 dans les class derivées alors pourquoi la declarer virtual ?

 

```

class Base {

virtual int f() const { return 0; }

};

class Derived : public Base {

int f() const { return 7; }

};

```

----------

## Magic Banana

 *netfab wrote:*   

> Tu peux déclarer ta variable mutable.
> 
> ```
> 
> class Derived : public Base {
> ...

 

C'est effectivement une solution... mais là, pour le coup, c'est vraiment pas beau.

 *netfab wrote:*   

> Tout dépend de l'appel :
> 
> ```
> 
> class Derived {
> ...

 

Cela confirmerait donc ce que je supposais : quand une méthode est appelée sur un objet déclaré "const", ce ne peut être que la méthode "const" qui est utilisée. Si l'objet n'est pas déclaré "const", l'autre méthode (non "const") est choisie. L'ennui, c'est que cela n'a pas l'air d'être vrai lorsque la méthode "const" est héritée (mon problème de départ où c'est la méthode "const" qui est choisie par le compilateur).

----------

## netfab

 *Quote:*   

> 
> 
> C'est effectivement une solution... mais là, pour le coup, c'est vraiment pas beau.
> 
> 

 

Je trouve cela très élégant au contraire. Cela colle parfaitement à la description que tu as faite avec tes cris d'animaux.

Tu voudrais redéfinir une méthode déclarée const de façon à ce qu'elle puisse modifier ponctuellement une donnée membre de ton instance, c'est exactement le but de mutable.

Maintenant, c'est sûr que cela doit rester une exception, si tu t'amuses à déclarer mutable toutes les variables de tes classes dérivées, il faut se poser des questions sur l'utilité du const sur tes fonctions originales.

 *Quote:*   

> 
> 
> L'ennui, c'est que cela n'a pas l'air d'être vrai lorsque la méthode "const" est héritée (mon problème de départ où c'est la méthode "const" qui est choisie par le compilateur).
> 
> 

 

Dans ton problème de départ, ton pointeur est de type Base, le compilateur regarde donc d'abord dans la classe Base (car c'est son type statique) pour trouver une fonction f(). Il trouve f() const.

Ne trouvant pas de méthode correspondante redéfinie dans la classe Derived (son type dynamique) car signatures différentes, le compilateur ne va pas plus loin.

C'est comme cela que j'interprète les choses, je ne vois pas de problème.

----------

## Magic Banana

 *netfab wrote:*   

>  *Quote:*   
> 
> L'ennui, c'est que cela n'a pas l'air d'être vrai lorsque la méthode "const" est héritée (mon problème de départ où c'est la méthode "const" qui est choisie par le compilateur).
> 
>  
> ...

 

Tu as raison.

----------

