Le Polymorphisme

C++ -

a marqué ce sujet comme résolu.

Bonjour à tous,

J'ai commencé (il y a 1 jour, 4 heures) la rédaction d'un tutoriel dont l'intitulé est Le Polymorphisme.

J'aimerai obtenir un maximum de retour sur celui-ci, sur le fond ainsi que sur la forme, afin de proposer en validation un texte de qualité.

Si vous êtes intéressé, cliquez ci-dessous

Merci d'avance pour votre aide

+0 -0

Salut,

Tu as mis tout le contenu dans l'introduction. Explications ici.

De plus, tu n'as pas inséré d'espace entre les dièses et le texte de tes titres, lesquels ne sont donc pas rendus correctement.

+0 -0

Quelques remarques après une première relecture rapide :

1. Il existe plusieurs polymorphismes en C++, tu devrais préciser duquel tu parles au moins une fois je pense.

2. Tu devrais préciser que tu parles de l'héritage publique et uniquement de lui.

3.

En effet, alors que la fraise "contient" un Fruit

Ici le contient me gène, j'aurais tendance à utiliser ce mot pour qualifier une relation d'héritage privé ou de sous-objet membre. Pour un héritage publique (en C++) j'utiliserais est un.

4. Tu devrais dire un mot sur le LSP quelque part, AMA.

5.

Le mot clé virtual permet de remplacer (à la construction de l'objet) la méthode de la classe mère ayant le même nom AINSI que la même signature.

Redéfinir est plus précis que remplacer. Que veux-tu dire par à la construction de l'objet ? Qu'entends-tu par signature ? Pour le C++, la notion de signature d'une fonction membre, non template, inclut entre autre le nom et les types des paramètres, pas celui du type de retour. Alors que virtual impose aussi une contrainte sur le type de retour (il n'est cependant pas obligé d'être identique, c.f. retour covariant).

+0 -0

Complètement HS :

  • en français, on met un espace avant et après les signes de ponctuation double (: ; ? etc) et seulement après pour les signes de ponctuation simple (. , etc).
  • Pas de majuscule avec : Pourquoi des majuscule en plein milieu de certaines phrases ? (effet de style ?)
  • C++ et pas c++

Je te conseille de passer ton texte dans un correcteur grammatical, par exemple bonpatron.com

Sur le fond :

Bien que je suppose que vous savez déjà ce qu'est l'héritage, je vous propose un petit rappel:

En fait, cette partie représente la moitié de l'article, ce n'est donc pas un petit rappel. (mais ce n'est pas une mauvaise chose, au contraire)

Lors de la création de class, vous utilisez souvent (voir même toujours) le mot clé public pour les méthodes et le mot clé private pour les attributs et quelques méthodes sensibles afin de respecter l'encapsulation.

L'encapsulation n'est pas simplement mettre les variables en privée et ajouter des accesseurs. Ne pas donner cette impression au lecture.

std::string getCouleur(){}

Si tu ne donnes que la déclaration, ne mets pas le bloc. Si tu veux donner la définition, mets le bloc complet (ou au moins 3 points pour indiquer que tu n'as pas mit le code).

De plus, pour un getter, mettre les const et référence :

1
2
3
const std::string & getCouleur() const;
// voire
constexpr std::string & getCouleur() const noexcept;

Voyons ce que l'héritage apporte lors de l'utilisation de pointeur:

C'est plutôt les pointeurs/références qui "apportent" quelque chose à l'héritage (le polymorphisme justement - on peut faire de l'héritage sans pointeur/référence, mais pas de polymorphisme dans ce cas).

Parler de type dynamique et type statique ?

Pourquoi des pointeurs et pas des références ? Pourquoi pas des pointeurs intelligents ?

Cela est dû au LSP:

Non. Le LSP ne justifie pas cette erreur de compilation (je dirais que c'est tout simplement la norme C++ qui interdit cela)

Le LSP est une règle que doit s'imposer les développeurs pour utiliser "correctement" l'héritage public (ie avoir un code qui soit le plus évolutif, maintenable, etc. possible)

Lorsque l'on déclare la même méthode à la fois dans une class fille que dans une class mère, le programme peut ne pas se comporter de la façon voulue si notre objet instancié à partir de la class fille est utilisé via un pointeur de type class mère.

HS : manque des "e" à tes classes.

Pour commencer, non. Ce n'est pas un problème de masquage. Si on écrit pas la fonction dans la classe enfant (donc pas de masquage) et que l'on appelle la fonction sur le type statique parent, cela plante :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class A {};
class B : A {
  void f();
};

A a;
a.f(); // erreur

A* a = new B;
a->f(); // erreur

Le masquage est lorsque l'on définie une fonction dans une classe enfant avec le même nom qu'une fonction de la classe parent, la fonction de la classe parente n'est plus accessible (sauf si on utilise using). Dans certain cas, le masquage est ce que l'on veut obtenir. Par exemple :

1
2
3
4
5
6
7
struct A {
  constexpr string & static_type() const noexcept { return "A"; }
};

struct B : A {
  constexpr string & static_type() const noexcept { return "B"; }
};

(Totalement inutile dans ce cas, mais osef…)

L'autre chose, c'est que ce n'est pas cette forme de masquage qui pose problème, c'est le masquage avec des fonctions qui porte le même nom, mais des paramètres différents :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct A {
  void f(int);
};

struct B : A {
  void f(string); // masque f(int)
};

B b;
b.f(1); // erreur, f(int) est masqué

Dans l'exemple que tu donnes, Fraise::presentation masque bien Fruit::presentation, mais si le code suivant n'affiche pas la même chose, ce n'est pas à cause du masquage, mais du fait que les types statique sont différents (et donc la fonction appelée est différente).

(HS : quand on est dans une hiérarchie de classes, avoir plusieurs fonctions avec le même nom produit un masquage. Avec des fonctions libres, on a une surcharge - overload (corrige moi Luc si je me trompe encore sur les termes), les 2 fonctions sont accessibles en même temps et le compilateur choisit celle qui correspond aux arguments d'appel de la fonction).

Et oui , Le soucis ici: La résolution statique des liens. Lors de la compilation, le compilateur sait que fraise est une fraise. mais dans le cas du pointeur , il attends un Fruit et non une Fraise, ainsi , lors de la compilation, impossible de savoir si on pointera sur un Fruit ou une Fraise… du coup le compilateur prévoit l'utilisation de la méthode de la class mère. D'ailleurs, si vous crée une nouvelle méthode dans la Class Fraise et que vous essayé de l'appeler via le pointeur, sa ne compilera tout simplement pas car elle n'existera pas dans la class mère. Il y a une astuce que je vous donnerai plus bas.

Confus je trouve.

A l'aide du polymorphisme , nous allons aider l'ordinateur à retrouver la bonne méthode à appeler lorsque que l'on passe une fille dans un ptr de sa Class mère.

Je trouve que "aider le compilateur" est maladroit. Ce n'est pas qu'il n'arrive pas à "trouver la bonne méthode", cela peut être un comportement voulu. C'est une nouvelle "fonctionnalité", qui permet d'appeler une fonction en fonction du type dynamique et pas du type statique. (donc fonction non virtuelle + pas de pointeur/référence = résolution de l'appel des fonctions selon le type statique ; fonction virtuelle + pointeur/référence = résolution de l'appel des fonctions selon le type dynamique).

Et pas se focaliser sur les pointeurs, le polymorphisme marche aussi avec les références.

Voila, ce petit mot magique viens de vous sauver la vie!

J'aime pas la magie en programmation. En particulier parce que je comprends rien à la magie et que pas comprendre quelque chose en programmation me pose problème…

Mise en garde : Le virtual ne se situe QUE dans le Header. donc lorsque vous virtualisé , ce sont que les prototype qui possède le mot clé , pas l'implémentation , capiche?! sinon je mords!

HS : j'aime pas le ton "je parle à mes copains" dans un article, c'est une des choses que je déteste dans les tutos du SdZ.

header = fichier d'en-tête

"vous virtualisé" : il y a une faute d'accord et cela veut rien dire.

Remarque : pour éviter le masquage d'une fonction virtuelle, penser aux mots-clés override et final

si on essayait de mettre un cout dans les destructeurs pour voir ce qu'il s'y passe?

HS : je le fais aussi, mais dans l'absolue, cout n'est pas noexcept, donc ne devrait pas être appelé dans un destructeur (sauf erreur de ma part… mais je chipote)

le code

Problème d'indentation

Pour palier à ce problème, la même solution que plus haut: virtualiser le destructeur.

Peut être rappeler la règle : "public et virtuel ou privé et non virtuel" pour les classes qui peuvent être dérivée (sinon, interdiction de dériver)

Les collections hétérogènes

Aussi les références (avec std::reference_wrapper)

Le design pattern Visitor, c'est pas mal avec les collections hétérogènes… (et cela t'évitera de trop parler du dynamic_cast)

std::string getCouleur(){} // La, ICI! Vous voyez pas?

J'ai l'impression finalement que c'était volontaire de donner une implémentation vide… ce qui n'est pas une bonne idée. Un compilateur correctement configuré te fera la remarque que ta fonction devrait retourner quelque chose.

Ceci étant mon premier tuto écrit, merci de ne pas me fouetter en place public si cela ne vous est pas compréhensible.

Note finale pas nécessaire, le tuto devra être compréhensible lorsqu'il sera publié. Sinon, cela ne sert pas à grand chose

En tout cas, bonne initiative comme tuto

(HS : désolé pour la longueur. On t'avait pas prévenu que dès que l'on parle de C++, il y a quelques casse-pieds qui relisent tout en détail ? :) )

+2 -0

Merci beaucoup , je vais approfondir tout ça car il y a des choses dont tu me parle que je ne suis pas trop à l'aise avec ^^. Non on ne m'avais point prévenus x). C'est ce qu'il faut , des "casse pieds" , c'est grâce à eux que la rigueur est là :). Et mine de rien. je vais bosser corriger tout ce que tu m'a dis.

Pour les majuscules en pleine phrase, je ne sais pas pourquoi c'est un automatisme, à trop coder et écrire des variables, j'en viens à le faire dans mes phrases. je dois avoir un truc décâblé la haut… XD

+0 -0

Lecture très rapide et en diagonale. Je me contenté de mot-clés que j'ai détectés et de bouts de codes. Je ne suis pas rentré dans le détails des explications. Je garde ça pour une autre fois. Désolé ^^'

  • je vois le masquage comme une surcharge dégénérée. Mais ce n'est pas le vocabulaire officiel.

  • Je déteste la vision 80's des 3 héritages qui agissent sur les niveaux de visibilité. C'est syntaxique et au final cela n'apprend pas quand se servir de quel héritage. Cf FAQ -> public pour la substituabilité, privé pour l'import seul de code.

  • En C++, méthode est un terme impropre qui selon les définitions veut dire fonction membre virtuelle -> fonction membre

  • Il y a effectivement 4 polymorphismes en C++. Et ils s'inscrivent dans un aspect plus large de points de variations et trucs communs ; re-cf FAQ

  • Polymorphisme = plusieurs formes, encore une définition très 80's qui ne veut pas dire grand chose. Je préfère la définition qu'avait pondu Jean-Marc et que l'on a mise dans la FAQ. Pour le côté étymologique, il faudrait relire l'article d'Alan Kay, en creusant bien, cette notion de forme peut apparaitre, mais je trouve qu'elle s'applique mal aux définitions que l'on veut donner régulièrement.

  • évite de séparer déclaration et initialisation, cf. FAQ pour le pourquoi

  • protected n'est pas la panacée. En abuser, c'est comme abuser des accesseurs et mutateurs, et cela revient à mal encapsuler, voire décapsuler.

En gros, je conseille vivement une lecture de la FAQ C++ de développez pour des aspects théoriques qui ne sont pas si négligeables au final. Ainsi, il y a aura moyen de se sortir de certains définitions/présentations très 80's/syntaxiques qui malheureusement s'accordent mal avec le C++ qui voudrait que l'on sache où l'on va – p.ex. le LSP est extrêmement important quand on parle de polymorphisme, en revanche savoir ce que deviennent les niveaux de visibilité, c'est un détail assez inutile en fin de compte vu que l'on ne devrait avoir que deux niveaux (dans l'idéal) : public pour l'interface, et privé pour nos détails auxquels nos enfants ne doivent pas avoir accès pour éviter de mettre en périls les invariants. (Tu peux voir que j'ai des principes forts qui limitent ce que la syntaxe me permet)

Avant une présentation de l'héritage (qui est un détail), je présenterai où l'on veut aller, et donc un exemple informel de polymorphisme.

Typiquement, je présente aujourd'hui le polymorphisme de la sorte: http://openclassrooms.com/forum/sujet/comment-le-c#message-87210210

Tu noteras que je parle d'abord de la théorie chapeau sur les commonalities/variability points. Tu noteras aussi que je prends un exemple de trucs substituables qui appellent à des comportements. J'évite le piège de prendre des agrégats de données car il est facile de perdre ceux qui ne voient pas où l'on veut en venir – et donc ouste les profs et élèves et personnes, ouste aussi les trucs abstraits qui ne s'inscrivent pas dans une procédure globale. Je me méfie également des choses qui scalent mal et qui finissent par demander de mettre en place des systèmes de traits – typiquement toutes les classes et races de personnages dans des jeux –, et qui ont conduit à l'apparition des ECS.

Truc sournois dans ma présentation, je dégaine le Principe Ouvert Fermé (a.k.a. OCP) sans trop le dire – la formulation est arrivée après, mais le principe participe à la genèse du polymorphisme. En effet, à la procédurale brutale les points de variabilité se font avec des ifs. Mais rajouter le support de nouveaux appareils de nettoyage se fait mal avec des ifs car il faut retrouver tous les points de variations et tous les patcher pour rajouter des nouveaux cas. Plutôt que de modifier le code, on permet à la place de s'adapter à de nouvelles choses. On utilise des points de variabilité ouverts.

Là, plusieurs façons en fonction de la dynamique du point de variabilité. Est-ce qu'il propre à des configurations (-> #if defined(...) – qui n'est pas du tout ouvert ; ou ajustement des Makefiles) ? Est-ce qu'il est propre à des exécutions (-> choix dynamique de la bibliothèque dynamique à utiliser) ? Est-il connu au moment de coder et sans variation dynamique (-> généricité via macros/template) ? Ou est-il totalement dynamique (-> polymorphisme d'inclusion) ? (Je n'ai repris qu'en vitesse un des points importants du livre/thèse de Jim O. Coplien (oui, le monsieur de sa forme canonique orthodoxe) : Multiparadigm Design in C++. Un résumé et des liens sont dispos dans la FAQ de dvpz.

À ce stade là, le public a eu un rappel sur un besoin courant : le support de points de variabilité, et on lui a indiqué qu'il y a des façons ouvertes (plug'n'play) d'en supporter. On peut alors enchainer sur le polymorphisme d'inclusion. Et donc parler qu'un prérequis dans les langages OO mainstream pour disposer du polymorphisme d'inclusion est le sous-typage. Celui-ci se réalise en C++ par héritage public. Et enchainer ensuite que pour avoir une adaptation dynamique des comportements, on a besoin d'une liaison tardive. En C++ on a la liaison dynamique via fonctions virtuelles – il manque plein d'étapes là.

(je dois pouvoir être contredit sur des aspects théoriques, c'est néanmoins ma vision actuelle de la chose relativement au C++)

Après il faudrait aussi aborder le LSP car c'est lui qui nous diras si un héritage public est bon ou bancal. L'exemple que j’emploie aujourd'hui est le suivant (jusqu'à ce que j'en trouve un meilleur car il me plait moyennement): http://luchermitte.github.io/blog/2014/05/24/programmation-par-contrat-un-peu-de-theorie/#iv--parenthse-oo--ppc--principe-de-substitution-de


  • Pour revenir à ton exemple, je le classe en agrégat de propriétés ce qui n'est pas idéal pour présenter la pertinence du polymorphisme, et encore moins le LSP.

  • le masquage ne marche pas
    Le masquage marche toujours. En revanche il n'y a pas d'adaptation du comportement au type dynamique comme on aurait pu l'espérer.
    Le masquage … masque. Et empêche de voir la fonction parente depuis le type fils.

  • ez pour la conjugaison à la deuxième personne du pluriel. Au bout de plusieurs, les é piquent les yeux. %-}

  • Pour les collections hétérogènes, le vector<T*> est à peine mieux que le T**. Vraiment mieux, c'est boost::ptr_vector<T> et std::vector<std::unique_ptr<T>>.

  • Et ce qui va nous intéresser pour les collections, hétérogènes ou pas, c'est de les passer à un même bloc de code commun. Si on n'est pas capable de trouver une telle procédure commune qui va s'appliquer à tous les éléments de la collection, c'est que ces éléments n'ont rien à faire ensemble.

  • Un vieux pinaillage sur la définition de classe abstraite: http://www.generation-nt.com/reponses/terminologie-classe-abstraite-entraide-211785.html

Ce sujet est verrouillé.