Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
[C++] Polymorphisme et const (résolu)
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index French
View previous topic :: View next topic  
Author Message
Magic Banana
Veteran
Veteran


Joined: 13 Dec 2005
Posts: 1906
Location: Belo Horizonte, Minas Gerais, Brasil

PostPosted: Wed Jan 19, 2011 10:28 pm    Post subject: [C++] Polymorphisme et const (résolu) Reply with quote

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é :
Code:
#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
Back to top
View user's profile Send private message
k-root
Guru
Guru


Joined: 08 Jan 2005
Posts: 426

PostPosted: Wed Jan 19, 2011 11:22 pm    Post subject: Reply with quote

+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
_________________
boozo wrote:
Gentoo, ça mange des ours et baffe des buffles par 37°C avec un bob et des tongs
Back to top
View user's profile Send private message
Poussin
l33t
l33t


Joined: 08 Jun 2007
Posts: 659
Location: Liège

PostPosted: Wed Jan 19, 2011 11:41 pm    Post subject: Reply with quote

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!
Back to top
View user's profile Send private message
k-root
Guru
Guru


Joined: 08 Jan 2005
Posts: 426

PostPosted: Wed Jan 19, 2011 11:57 pm    Post subject: Reply with quote

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++
_________________
boozo wrote:
Gentoo, ça mange des ours et baffe des buffles par 37°C avec un bob et des tongs
Back to top
View user's profile Send private message
netfab
Veteran
Veteran


Joined: 03 Mar 2005
Posts: 1677
Location: 127.0.0.1

PostPosted: Thu Jan 20, 2011 12:28 am    Post subject: Reply with quote

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).
Back to top
View user's profile Send private message
k-root
Guru
Guru


Joined: 08 Jan 2005
Posts: 426

PostPosted: Thu Jan 20, 2011 1:03 am    Post subject: Reply with quote

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.
_________________
boozo wrote:
Gentoo, ça mange des ours et baffe des buffles par 37°C avec un bob et des tongs
Back to top
View user's profile Send private message
Magic Banana
Veteran
Veteran


Joined: 13 Dec 2005
Posts: 1906
Location: Belo Horizonte, Minas Gerais, Brasil

PostPosted: Thu Jan 20, 2011 2:44 am    Post subject: Reply with quote

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).
Back to top
View user's profile Send private message
k-root
Guru
Guru


Joined: 08 Jan 2005
Posts: 426

PostPosted: Thu Jan 20, 2011 8:53 am    Post subject: Reply with quote

faites des interfaces ...
_________________
boozo wrote:
Gentoo, ça mange des ours et baffe des buffles par 37°C avec un bob et des tongs
Back to top
View user's profile Send private message
netfab
Veteran
Veteran


Joined: 03 Mar 2005
Posts: 1677
Location: 127.0.0.1

PostPosted: Thu Jan 20, 2011 9:45 am    Post subject: Reply with quote

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.
Code:

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 :
Code:

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
7


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 ?

Code:

class Base {
virtual int f() const { return 0; }
};

class Derived : public Base {
int f() const { return 7; }
};
Back to top
View user's profile Send private message
Magic Banana
Veteran
Veteran


Joined: 13 Dec 2005
Posts: 1906
Location: Belo Horizonte, Minas Gerais, Brasil

PostPosted: Thu Jan 20, 2011 10:56 am    Post subject: Reply with quote

netfab wrote:
Tu peux déclarer ta variable mutable.
Code:

class Derived : public Base {
  mutable int i;
public:
  Derived() : i(0) {}
  int f() const { return ++i; }
};


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

netfab wrote:
Tout dépend de l'appel :
Code:

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
7

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).
Back to top
View user's profile Send private message
netfab
Veteran
Veteran


Joined: 03 Mar 2005
Posts: 1677
Location: 127.0.0.1

PostPosted: Thu Jan 20, 2011 12:18 pm    Post subject: Reply with quote

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.
Back to top
View user's profile Send private message
Magic Banana
Veteran
Veteran


Joined: 13 Dec 2005
Posts: 1906
Location: Belo Horizonte, Minas Gerais, Brasil

PostPosted: Thu Jan 20, 2011 6:11 pm    Post subject: Reply with quote

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).

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.

Tu as raison.
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index French All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum