Langage C++/Classe

Leçons de niveau 14
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du chapitre
Classe
Icône de la faculté
Chapitre no 14
Leçon : Langage C++
Chap. préc. :Objet
Chap. suiv. :Sommaire
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Classe
Langage C++/Classe
 », n'a pu être restituée correctement ci-dessus.

La classe[modifier | modifier le wikicode]

En programmation orientée objet, tout est basé sur le concept de la classe. La classe est une entité autonome capable de maintenir, une fois instanciée (définie), la cohérence des données qu'elle est chargée d'entretenir. (nous mettrons en évidence la syntaxe dans la partie implémentation)


Les visibilités : niveaux de visibilité dans une classe[modifier | modifier le wikicode]

Il existe trois niveaux de visibilité dans une classe :

  • Privé (Niveau par défaut) : Permet de masquer complètement les données et méthodes à des classes tierces et même aux classes dérivées. On parle d'encapsulation (peut être utilisé par les méthodes mais est surtout destiné aux variables d'une classe).
  • Protégé : permet de partager des données uniquement aux classes dérivées. (Normalement uniquement utilisé pour les méthodes)
  • Public : permet de partager les données avec toutes les classes tierces. (Normalement uniquement utilisé pour les méthodes)

Les attributs : variable de classes[modifier | modifier le wikicode]

La classe est une entité qui peut être composée de variables internes que l’on nomme attributs membres.

La théorie veut que les attributs d'une classe ne soient accessibles directement qu’à cette classe. Bien qu'en C++ il soit possible de définir des attributs membres de visibilité publique ou protégée, il est normalement indispensable de rendre les attributs membres privés (je n'ai jamais eu à rendre publiques ou même protégés des attributs).

En effet le paradigme-objet étant de rendre la classe responsable de la gestion de ses attributs pour assurer la gestion correcte et la cohésion des données, il serait mal vu qu'une autre classe accède malencontreusement à ces attributs et vienne bouleverser l'organisation que la classe permet de maintenir entre eux. Quand une classe privatise un attribut on dit qu'elle l'encapsule.

Les méthodes : méthodes de classes[modifier | modifier le wikicode]

Nous avons déjà vu les méthodes auparavant.

Les méthodes déterminent le comportement qu'une classe est capable de réaliser. La classe possède au moins trois méthodes spéciales :

  • Un constructeur sans paramètres appelé aussi constructeur par défaut ou, selon le cas, un constructeur paramétré qui oblige à fournir des paramètres pour créer la classe.
  • Un constructeur par copie qui prend en paramètre une référence sur la même classe (dans la majorité des cas elle est fournie par défaut par le compilateur mais il peut être fréquemment nécessaire de la définir manuellement).
  • Un destructeur qui est chargé d'appliquer un traitement pour éventuellement nettoyer la mémoire. Bien qu’il soit souvent vide, l'implémenter explicitement et systématiquement permet de faire fonctionner le mécanisme d'héritage polymorphe.

Les méthodes donnant accès aux attributs sont appelés suivant le sens d'accès :

  • Des accesseurs pour les méthodes accédant en lecture seule.
  • Des mutateurs pour les méthodes accédant en écriture seule.
  • Des propriétés pour les méthodes accédant en écriture et en lecture.

La théorie veux que tout attribut qui aurait besoin d’être transmis ou modifié, le soit par l'une de ces méthodes d'accès. (Je n'ai jamais eu à faire autrement.) Les méthodes définissent aussi l'interface d'une classe.


Généralisation de classes[modifier | modifier le wikicode]

En programmation orientée objet, on peut structurer une hiérarchie de classes en arborescence. Les classes du sommet de l'arbre sont les classes les plus abstraites et générales. Les classes les plus profondes sont les plus concrètes et les plus spécialisés.

Pour illustrer ces propos supposons les figures suivantes :

  • Carré
  • Rectangle
  • Parallélogramme
  • Losange
  • Quadrilatère
  • Cercle
  • Ovale

Imaginons maintenant que nous devions réaliser des classes basées sur ces figures. Supposons que chaque classe doit être capable de dessiner. Supposons aussi que nous devions gérer chacune de ces classes avec le même (et unique) pointeur et appeler la même méthode pour le dessin de chaque classe.

Dans les figures imposées on peut voir que "Carré" est un cas particulier de (ou "une sorte de") "Rectangle" qui est lui-même un cas particulier du "Parallélogramme" qui est lui-même un "Quadrilatère". Quant à "Losange" c’est un "Quadrilatère".

On peut voir aussi que "Cercle" est un cas particulier de "Ovale".

Cependant rien ne lie Ovale et Quadrilatère.

Nous allons donc devoir généraliser Ovale et Quadrilatère en "Figure"

Ainsi Quadrilatère et Ovale sont des Figures, ainsi un pointeur sur Figure est capable de manipuler n’importe quelle classe sous-jacente, puisque, après tout, un carré est une figure tout comme un cercle.

Traitons maintenant le problème de la méthode unique. En fait depuis que l’on a créé la classe Figure ce n'est plus un problème.

En effet il suffit de définir la méthode "Dessine()" dans la classe Figure pour que toutes les autres classes en héritent.

Abstraction de classes[modifier | modifier le wikicode]

Lors de généralisations successives il est courant de se rendre compte qu'une classe est trop "abstraite" pour avoir suffisamment de données exploitables pour pouvoir en générer une instance. C'est le cas pour notre classe "Figure". En effet elle est trop générique et rien d'intéressant ne peut en sortir, cependant elle fournit une interface que toutes les autres classes devront reproduire fidèlement. La méthode "Dessiner" ne représente pas grand chose pour une Figure. Nous ne pouvons pas décrire de comportement pour cette méthode dans cette classe. Nous ne pouvons donc pas instancier la classe car cela n'aurait pas de sens. Nous allons donc devoir rendre cette classe abstraite. Cela signifie que la classe n’est pas instanciable en l'état mais qu'une classe héritée non abstraite peut être interprétée comme cette classe (via l'utilisations de pointeurs ou de références).

Héritage de classes[modifier | modifier le wikicode]

L'héritage est la faculté qu’à une classe de pouvoir transmettre ses attributs et méthodes à ses classes dérivées et, sous certaines conditions, permettre à ses classes dérivées de redéfinir ses méthodes pour pouvoir les améliorer et les spécialiser.

Tout d’abord il faut savoir qu'en C++ une classe dérivée reçoit toujours une copie de l'intégralité des attributs et des méthodes de sa classe ancêtre. En C++, bien que l’on puisse choisir la façon dont sont copiés les membres de la classe ancêtre, la théorie-objet veux que l’on hérite toujours des classes ancêtres de manière publique afin que tous les membres publics de la classe ancêtre soient aussi disponibles de manière publique dans la classe dérivée. L'héritage permet de ne pas réécrire éternellement les mêmes codes, de réutiliser les objets, de pouvoir les spécialiser et mettre en œuvre le polymorphisme de classe.

Polymorphisme de classes[modifier | modifier le wikicode]

Le polymorphisme en informatique se traduit par la capacité qu'a un pointeur de classe ancêtre présentant une interface donnée, à appeler la méthode de l'instance de la classe dérivée correspondant à la méthode de la classe ancêtre. En C++ la mise en œuvre du polymorphisme se fait à l'aide du mot clé "virtual". Dans la pratique et pour reprendre l'exemple vu précédemment :

Si l’on créé un pointeur sur Figure que l’on lui assigne l'adresse de l'instance d'un carré et que l’on demande au pointeur-figure de se dessiner alors le pointeur va appeler la méthode virtuelle et par le biais de l'héritage virtuel appeler la méthode implémentée dans la classe Carré.

Implémentation[modifier | modifier le wikicode]

Les classes sont donc la représentation logique du concept d'objet. Voici en C++ comment implémenter ces classes conformément à la théorie de l'objet.


Où <NomNouvelleClasse> est le nom de la classe, <ClasseAncêtre> est une classe ancêtre tout comme <AutreClasseAncêtre> et sont facultatives si la classe n'a pas à avoir d'ancêtre, <TypeAttribut1> et <TypeAttributN> sont les types des attributs, <NomAttribut1> et <NomAttributN> sont les noms des attributs, les attributs sont facultatifs, <TypeParamettre> et <NomParamettre> sont les paramètres des méthodes de la classe, <TypeMethode1> et <TypeMethodeN> sont les types de retours des méthodes de la classe, <NomMethode1> et <NomMethodeN> sont les noms des méthodes de la classe, , <Visibilitée> peut prendre trois valeurs: public, protected ou private (par défaut). Un membre déclaré public peut être manipulé par n’importe quelle classe, un membre protected ne peut être manipulé que par la classe et ses dérivées tandis qu'un membre private ne peut être manipulé que par les méthodes de la classe.

Les méthodes sont soit définies directement dans la déclaration de classe (auquel cas ce sont des Macros), soit définies en dehors de la déclaration dans un fichier source séparé de la manière suivante :


Voici un exemple de la classe la plus simple à réaliser. Il faut dire aussi qu'elle ne fait strictement rien.

Début de l'exemple
Fin de l'exemple


Exemples:[modifier | modifier le wikicode]

Maintenant reprenons nos exemples de figures de tout à l’heure.

Début de l'exemple
Fin de l'exemple