
Le terme polymorphisme décrit la caractéristique d'un élément qui peut prendre plusieurs formes, comme l'eau qui se trouve à l'état solide, liquide ou gazeux.
En informatique, le polymorphisme désigne un concept de la théorie des types, selon lequel un nom d'objet peut désigner des instances de classes différentes issues d'une même arborescence. Effectivement, nous avons découvert que les classes issues d'une même hiérarchie sont compatibles. Ainsi, un objet de la classe Personne peut faire référence à un objet de la classe Elève (puisqu'un élève est aussi une personne). Nous pouvons donc écrire :
Personne &p = Elève( « Lagafe » , « Gaston » ) ;L'objet p peut aussi bien faire référence à une personne qu'à un élève ou à tout autre classe créée ultérieurement (comme la classe Professeur) faisant partie de cette hiérarchie. C'est ce principe là qui offre une grande richesse à la programmation orientée objet.
Visualisons ce concept en prenant l'exemple de la gestion d'une classe d'élèves. Nous créons donc une classe dénommée Classe. Cette classe dispose d'une méthode description() qui doit permettre de visualiser les caractéristiques de chacune des personnes constituant la classe.

Les interactions entre objets sont écrites selon les termes des spécifications définies, non pas dans les classes dérivées des objets, mais dans leurs classes de base. Cela permet d'écrire un code détaché des particularités de chaque classe, et d'obtenir des mécanismes suffisamment généraux pour être valides dans le futur, quand seront créées de nouvelles classes.
Le terme polymorphisme désigne en fait le polymorphisme du comportement, c'est-à-dire la possibilité de déclencher les méthodes différentes en réponse d'un même message. Chaque classe dérivée hérite de la spécification des méthodes de ses classes de base, mais a aussi la possibilité de modifier localement le comportement de ces méthodes, afin de mieux prendre en compte les particularités de chacun. C'est le principe même de la redéfinition des méthodes comme affiche().
De ce point de vue, une méthode donnée est polymorphe puisque sa réalisation peut prendre plusieurs formes. Le polymorphisme est un mécanisme de découplage qui agit dans le temps. Les bénéfices du polymorphisme sont avant tout récoltés durant la maintenance.
D'un point de vue pratique, et en reprenant l'exemple de la gestion d'une classe d'élèves, le polymorphisme consistera donc à redéfinir correctement les méthodes affiche() de chacune des classes faisant parties de la hiérarchie. Lorsqu'une nouvelle classe est créée, si elle veut s'intégrer dans le polymorphisme, c'est-à-dire, pouvoir participer à la gestion de la classe d'élève, elle aura pour contrat, de redéfinir sa propre méthode affiche().
Par défaut, et contrairement au langage Java, les classes créées dans une hiérarchie dans le langage C++ n'intègrent pas le polymorphisme. Ici, pour notre exemple, nous devons modifier le comportement général de la (ou des) méthodes afin qu'effectivement le polymorphisme soit opérationnel dans notre hiérarchie. Pour cela, certains critères doivent être respectés :
Dans la suite, nous allons découvrir pourquoi ces deux critères sont nécessaires.
Puisqu'il existe un lien de parenté entre les classes d'une même hiérarchie, nous avons découvert qu'il existe, du coup, une certaine compatibilité. Cette compatibilité consiste, dans le cas du langage C++ en un système de conversions implicites, mises en œuvres automatiquement. Ces conversions sont les suivantes :
Nous nous sommes déjà penché sur le premier cas, mais nous allons y revenir pour voir la répercussion sur le polymorphisme.


Reprenons le diagramme UML qui correspond normalement à la logique du polymorphisme. Vous avez sur la partie gauche le codage correspondant. Bien entendu, seules les méthodes qui nous concernent directement sont implémentées. Les autres méthodes n'ont pas été introduites ici.
Ci-dessous se trouve un scénario qui montre la conversion d'un objet dérivé vers un objet de la classe de base.

![]()
Que se passe-t-il dans ce programme ?
Pour être plus précis, rappelons que lorsque nous effectuons une opération quelconque, et à fortiori, une affectation, tous les opérandes de l'opération doivent être du même type. Comme le membre gauche de l'affectation est de type Personne, le membre droit doit être également de ce type. Du coup, l'objet anonyme de type Professeur est d'abord transformé en un objet anonyme de type Personne avec la perte d'une partie de ses constituants. Ensuite, nous avons une copie de l'objet anonyme de type Personne vers l'objet p. Au moment de cette copie, nous nous retrouvons donc qu'avec des objets de type Personne. Dans ce contexte, l'objet de type Professeur a totalement disparu.
Ligne 40 : p reste donc bien une Personne. La méthode affiche() qui est sollicité correspond bien à la méthode affiche() de la classe de base.
Conclusion : Dans ces conditions, il n'est absolument pas possible d'intégrer le polymorphisme. Tout ce que nous faisons, c'est une copie des membres d'une classe dérivée vers sa classe de base. L'objet lui-même n'a pas changé de statut. Il faut plutôt que l'objet de la classe de base fasse « référence » à un autre objet ; soit également à un objet de la classe de base, soit à un objet de la classe dérivée. Nous devons donc utiliser les références ( accès direct ) ou les pointeurs ( accès indirect ).
Deux nouvelles conversions standard (automatiques) sont prévues entre les classes de base et leurs classes dérivées :
Retentons l'expérience précédente, en utilisant cette fois-ci, par exemple, un pointeur sur un objet de type Personne.

![]()
Que se passe-t-il dans ce programme ?

Conclusion : Le problème, c'est que le choix de la méthode appelée est réalisée par le compilateur, ce qui signifie qu'elle est définie une fois pour toutes avant même que le programme ne s'exécute (la phase de compilation se fait avant la phase d'exécution du programme). Bien entendu, dans ces conditions, on comprend que le compilateur ne peut que décider de mettre en place l'appel de la méthode correspondant au type défini par le pointeur. Ainsi à la ligne 38, p est un pointeur de type Personne, donc à la ligne 40, le compilateur prend automatiquement la méthode Personne::affiche() .
Nous connaissons déjà les variables ou les objets dynamiques. Ces objets ne pas sont créés au moment de la compilation, mais uniquement lorsque nous en avons besoin. Nous utilisons pour cela les opérateurs new et delete, respectivement pour leur création et pour leur destruction. Ces objets sont alors créés dans le Tas. Par contre, seul l'état de l'objet est enregistré, c'est-à-dire, la valeur représentative de chacun des attributs.
Ce qu'il faudrait, c'est qu'il existe le même principe pour les méthodes. Il faudrait que le choix de la méthode correspondant au type de l'objet pointé soit déterminé uniquement au moment de son exécution et non pas, pendant la phase de compilation. Il faudrait donc un système de méthodes dynamiques. Ces méthodes existent, il s'agit des méthodes virtuelles.
Une méthode virtuelle est une méthode particulière invoquée au moyen d'un pointeur ou d'une référence sur une classe de base ; elle est liée dynamiquement au moment de l'exécution. L'instance invoquée est déterminée par le type de classe de l'objet adressé par le pointeur ou la référence. La résolution d'une méthode virtuelle est transparente à l'utilisateur.
Il suffit de placer le mot réservé virtual devant la méthode que nous désirons rendre virtuelle et le tour est joué. Par ailleurs, il n'est pas nécessaire de déclarer virtuelles les méthodes redéfinies dans les classes dérivées, elles le sont automatiquement.
Reprenons donc le scénario précédent en rajoutant juste virtual devant la méthode affiche() de la classe de base.



Cette fois-ci, l'affichage est celui prévu.
virtual void affiche() ;
Cette instruction indique au compilateur que les éventuels appels de la méthode affiche doivent utiliser une ligature dynamique et non plus une ligature statique
p->affiche() ;
Du coup, lorsque le compilateur rencontre cette instruction, il ne décidera pas de la méthode à appeler. Il se contentera de mettre en place un dispositif permettant de n'effectuer le choix de la méthode qu'au moment de l'exécution de cette instruction, ce choix étant basé sur le type exact de l'objet ayant effectué l'appel. Plusieurs exécutions de cette même instruction pouvant appeler des méthodes différentes.

Conclusion : Nous voyons que nous pouvons intégrer le polymorphisme vraiment très simplement. Il suffit de déclarer la ou les méthodes voulues de la classe de base comme virtuelles. La seule difficulté finalement, se situe au moment de la phase de conception durant l'élaboration des diagrammes UML. C'est effectivement à ce moment là qu'il faut décider si une hiérarchie de classes propose le polymorphisme ou pas. Dans l'affirmative, il est en effet souvent nécessaire de rajouter de nouvelles méthodes dans la classe ancêtre, alors que ce n'était pas spécialement prévu au départ.
En voyant cette simplicité, nous pourrions nous dire que nous n'avons pas besoin de nous poser autant de questions. Nous pouvons systématiquement spécifier toutes les méthodes comme virtuelles, puisque nous rajoutons un seul mot sur chacune des méthodes de la classe de base. L'étude suivante va nous montrer que ce n'est pas aussi simple.
Nous venons de voir que le polymorphisme est très simple à implémenter dans le langage C++. Nous pouvons nous passer de toutes autres connaissances subsidiaires. Toutefois, il peut être intéressant d'avoir une compréhension plus fine du mécanisme interne, en prenant connaissance de l'implantation de la ligature dynamique.
D'une manière générale, lorsqu'une classe comporte au moins une méthode virtuelle, le compilateur lui associe une table contenant les adresses des méthodes virtuelles correspondantes.
D'autre part, tout objet d'une classe comportant au moins une méthode virtuelle se voit attribuer par le compilateur, outre l'emplacement mémoire nécessaire à ses attributs, un emplacement supplémentaire de type pointeur, contenant l'adresse de la table associée à sa classe.

Nous pouvons ainsi dire que ce pointeur, introduit dans chaque objet, représente l'information permettant d'identifier la classe de l'objet. C'est effectivement cette information qui est exploitée pour mettre en œuvre la ligature dynamique. Chaque appel d'une méthode virtuelle est traduit par le compilateur de la façon suivante :
Conclusion : Si vous intégrez le polymorphisme, la structure interne se complexifie alors largement. Par ailleurs, vous remarquez que l'accès à une méthode passe par deux indirections, ce qui rallonge d'autant le temps de réponse. Pour finir, le stockage de ces différentes tables demande de la mémoire supplémentaire. Il ne faut donc pas qu'une hiérarchie soit considérée systématiquement comme polymorphique. Il faut que cela corresponde à un besoin, déterminé au moment de la phase de conception, notamment durant l'élaboration des diagrammes UML.
Dans ce chapitre, nous allons faire un certain nombre de remarques afin que les méthodes virtuelles soient correctement implémentées.
Les méthodes virtuelles doivent impérativement existées pour qu'elles puissent être adressées à l'aide de la table correspondante. Elles sont donc nécessairement non inline (si vous la déclarez inline, le compilateur fabrique une véritable méthode).
Le mot virtual se place uniquement dans la déclaration de la classe. Lorsque vous définissez la méthode à l'extérieur de la classe, vous ne devez plus re-spécifier le mot virtual devant la signature de la méthode.
La redéfinition d'une méthode virtuelle dans une classe dérivée doit réaliser une adéquation parfaite (nom, signature, et type de retour) avec la méthode virtuelle déclarée dans la classe de base. Il n'est pas nécessaire de re-spécifier le mot virtual. Si la re-déclaration dans la classe dérivée ne réalise pas une adéquation parfaite, la méthode n'est pas gérée comme une méthode virtuelle de la classe dérivée. Dans ce cas là, il s'agira tout simplement d'une surdéfinition.

