Classe et objet en C++ Encapsulation et cycle de vie Joel Falcou Programmation Orientée Objet - IFIPS Apprentissage Introduction Ce cours à pour but, après quelques rappels sur les fondamentaux de la programmation orientée objets, de fournir les éléments syntaxiques de bases pour permettre de définir classe et objet en C++. En outre, nous mettons en avant les différences fondamentales entre la manière dont JAVA et C++ gère la mémoire et l impact de ces différences sur le cycle de vie des objets. Il existe énormément de littérature sur les fondements philosophiques de la programmation orientée objet et il est bien entendu hors de la portée de ce cours (qui se veut relativement pratique et pragmatique) de reprendre ses discussions. Les curieux pourront se reporter aux ouvrages citées dans la parties références du cours. 1 Rappels 1.1 Notion d objet Depuis SIMULA, de nombreux langages se sont auto-proclamés orientés objets et ont fournis une définition plus ou moins ad hoc d un objet. Sans rentrer dans des querelles de clochers stériles, nous définirons l objet comme une entité encapsulant des données et des opérations applicables à ces données. Booch propose une définition plus formelle que nous adopterons pour la suite de cours. Il définit un objet comme étant soit : une chose palpable et/ou visible. Exemple : un crayon, un chien ; quelque chose d appréhendable intellectuellement comme la température ; quelque chose vers laquelle une action ou une pensée est dirigée. Dans le contexte du génie logiciel, un objet est donc considéré comme une entité qui présente un intérêt pour le problème courant. Un objet a donc naturellement un certain nombre de caractéristique qui définissent à la fois son état on parle alors d attributs et son comportement on parlera alors de méthodes. 1
Exemples divers une chaise est défini par sa couleur, son nombre de pied et sa position dans l espace. En terme de comportement, un chaise est capable d être créer, déplacée et détruite ; un chat est défini par sa robe, sa race, son age, sa taille et son poids. Un chat peut naître (i.e être construit), manger, dormir 1, se déplacer, se reproduire ou mourir (i.e être détruit) ; un nombre complexe est défini par sa partie réelle et sa partie imaginaires. En terme de comportement, on peut extraire d un nombre complexe sa partie réelle ou imaginaire, prendre son conjugué, calculé sa norme ou l utiliser au sein d opérations arithmétiques. Dans ces exemples, on note certains points communs comme par exemple le fait d être capable de construire ou de détruire un objet. En outre, on peut noter que, par exemple, pour un nombre arbitraire d objets chats il est possible de définir un modèle de chat. 1.2 Notion de classe Cette remarque nous amène alors naturellement au concept de classe. Un classe est définie comme étant un modèle décrivant une catégorie d objets partageant des attributs et des méthodes communes. Dans les exemples de la section précédente, on observe que tous les objets chaise, chat ou nombre complexe partage un comportement similaire et que le type de leurs attributs sont identiques même si leur valeurs effectives est différentes. Si l on considère alors une classe comme une structure de donnée, il est possible, à partir du modèle représenté par une classe, de créer des instances d objets. Il existe plusieurs façons de représenter une classe soit de manière abstraite comme avec UML par exemple, soit de manière concrète en utilisant la syntaxe d un langage objet. 1.3 Principe d encapsulation Pour l instant, la différence entre la programmation orienté objet et une forme évoluée de programmation impérative utilisant des structures de données plus complexes n est pas flagrante. En effet, si l on définit un objet comme une structure classique, rien ne nous empêche d aller directement modifier l état interne d un objet, rendant caduque la totalité du discours précédent sur l abstraction. Il est donc nécessaire de se discipliner. Un des principes de la programmation orienté objet est l encapsulation qui consiste à masquer à l utilisateur final les détails interne des objets qu il manipule. Pour ce faire, on défini la notion de visibilité. Chaque membre et méthode d une classe est ainsi défini comme étant soit publique soit privé. Un membre ou méthode publique est accessible par n importe quelle autre classe alors qu un membre ou méthode privée n est accessible qu au sein de la classe qui le définit. Qu en déduire donc? Et bien, le principe d encapsulation nous dit que, pour une classe donnée, il est de bon ton de définir ses attributs comme étant privés et de 1 Les mauvaises langues diront que c est à peu prés tout. 2
définir comme publique les méthodes qui définissent le comportement externe de la classe. 2 Cycle de vie des objets en C++ 2.1 Principe de la gestion de la mémoire Contrairement à JAVA, la gestion de la mémoire en C++ repose sur les principes hérités de son ancêtre C. Point de Garbage Collector et de gestion automatique de la durée de vie des variables. En C++, la totalité de ces processus doit être géré explicitement. Mais ne nous lamentons pas trop, nous verrons comment nous élever au dessus de ces considérations bassement matérielles dans les prochains chapitres.on distingue deux façons de créer un objet. Soit de manière statique, soit de manière dynamique. 2.2 Allocation statique L exécution d un programme est généralement structurée autour d appels de fonctions. Chacune de ces fonctions est doté d un segment de mémoire qui lui est propre et que l on nomme la pile. Les variables définies dans la portée 2 d une fonction sont allouées sur cette pile au moment de l appel de la fonction et libérées à la fin de cet appel.en C++, la création d un objet de type T se fait simplement de la manière suivante : T mon objet ; Listing 1 Exemple d allocation statique Sa destruction est ensuite prise en charge automatiquement à la sortie de la portée courante. Exercice Que fait ce programme? Listing 2 scope.cpp #i n c l u d e <iostream > // e n t r é e / s o r t i e système #i n c l u d e <s t r i n g > // Type chaine de c a r a c t è r e c l a s s Scope p u b l i c : Scope ( std : : s t r i n g const& n ) : name(n) std : : cout << Entering : << name << std : : endl ; Scope ( ) 2 voir plus loin la notion de scope 3
std : : cout << Exiting : << name << std : : endl ; p r i v a t e : std : : s t r i n g name ; ; double f ( i n t a, i n t b ) Scope s ( Fonction f ( ) ) ; return a/ double ( b ) ; i n t main ( ) Scope s ( Fonction main ( ) ) ; std : : cout << f ( 4, 5 ) << std : : endl ; return 0 ; 2.3 Allocation dynamique La plupart des programmes ayant des besoins en mémoire dépendant de l usage qu on en fait, il est nécessaire de pouvoir, à des moments arbitraires de l exécution, demander au système l allocation de nouvelles zones de mémoire, et de pouvoir restituer au système ces zones (libérer la mémoire). Dans ce cas, l allocation et la libération de la mémoire sont sous la responsabilité du programmeur et c est là ou commence les problèmes. En effet, il va falloir bien faire attention à ce que chaque allocation dynamique soit bien suivi d une et d une seule libération. Si la plupart des systèmes d exploitation sont néanmoins capable de passer derrière vous pour nettoyer vos saletés, certains le font assez mal. En outre, il n existe par contre aucune sécurité contre le problème de la double libération. En pratique, comment cela se passe-t-il? Soit une classe T que nous voulons instancier dynamiquement. L allocation par elle même se passe via l opérateur new T t = new T; Pour détruire un objet alloué dynamiquement, il faut faire appel à l opérateur delete. delete prend en paramètre le pointeur vers un objet alloué par new, appel son destructeur puis signale au système que la mémoire est libre d être réutilisée. 2.4 Comparaison statique vs dynamique L allocation statique doit être choisie en priorité lorsque les besoins mémoires sont connus à l avance et sont proportionnels à certains paramètres d entrée du 4
programme. L allocation et la libération consécutives, selon une discipline de pile, de grosses quantités de mémoire peuvent toutefois poser des problèmes de performance et de lisibilité du source (explosion du nombre de paramètres passés aux fonctions). Ces problèmes sont à mettre dans la balance contre l assurance d une libération correcte des ressources mémoire. L allocation dynamique, puisqu elle permet le contrôle complètement arbitraire de l allocation et de la libération, offre le plus de possibilités. Les ressources allouées manuellement ont cependant une durée de vie indéfinie, c est-à-dire que le programmeur a la responsabilité de la libération (et doit éviter de tomber dans les pièges de la double libération, etc.). 3 Ce qu il Faut Retenir 3.1 Classe et objet Une classe modèlise une famille de concept ; Un objet représente une instance d une classe ; L encapsulation permet d abstraire les détails interne d une classe. 3.2 Classe et objet en C++ Une classe se définit en C++ via le mot clé class ; Une classe C++ se déclare dans unf ichier.hpp et de définit dans un fichier.cpp ; Une déclaration de classe se compose de sections pourvues d une visibilité publique (public) ou privée (private). Une même classe peut contenir plusieurs sections de visibilité différentes ; Une déclaration de classe contient la liste des méthodes et des attributs de la dite classe ; La définition de la classe reprend chaque méthode et renseigne son comportement ; Chaque méthode définie est préfixée par le nom de la classe à laquelle elle appartient. 3.3 Cycle de vie des objets Un objet est crée par un appel à un de ses constructeurs ; Un objet est détruit par l appel à son destructeur ; Dans le cas d une allcoation statique, le destructeur est appelé automatiquement en sortie de portée de fonction ou d ebloc ; Dans le cas d une allocation dynamique, l objet est construit via l opérateur new et détruit explicitement par l appel à l opérateur delete. 5