Le langage C++
Classes
Classes et objets
Un objet est constitué par l’association d’une certaine quantité de mémoire, organisée en champs, et d’un ensemble de fonctions, destinées principalement µa la consultation et la modiflcation des valeurs de ces champs.
La déflnition d’un type objet s’appelle une classe. D’un point de vue syntaxique, cela ressemble beaucoup µa une déflnition de structure, sauf que
{ le mot réservé class remplace5 le mot struct, { certains champs de la classe sont des fonctions.
Par exemple, le programme suivant est une première version d’une classe Point destinée µa représenter les points a–chés dans une fen^etre graphique :
5En fait on peut aussi utiliser struct, voyez la section 2.2.5.
class Point {
public:
void afficher() {
cout << ’(’ << x << ’,’ << y << ’)’;
}
void placer(int a, int b) {
validation des valeurs de a et b;
x = a; y = b;
}
private:
int x, y;
};
Chaque objet de la classe Point comporte un peu de mémoire, composée de deux entiers x et y, et de deux fonctions : afficher, qui accède µa x et y sans les modifler, et placer, qui change les valeurs de x et y.
L’association de membres et fonctions au sein d’une classe, avec la possibilité de rendre privés certains d’entre eux, s’appelle l’encapsulation des données. Intér^et de la démarche : puisqu’elles ont eté déclarées privées, les coordonnées x et y d’un point ne peuvent ^etre modiflées autrement que par un appel de la fonction placer sur ce point. Or, en prenant les précautions nécessaires lors de l’écriture de cette fonction (ce que nous avons noté validation des valeurs de a et b ) le programmeur responsable de la classe Point peut garantir aux utilisateurs de cette classe que tous les objets crées auront toujours des coordonnées correctes. Autrement dit : chaque objet peut prendre soin de sa propre cohérence interne.
Autre avantage important : on pourra µa tout moment changer l’implémentation (i.e. les détails internes) de la classe tout en ayant la certitude qu’il n’y aura rien µa changer dans les programmes qui l’utilisent.
Note. Dans une classe, les déclarations des membres peuvent se trouver dans un ordre quelconque, m^eme lorsque ces membres se référencent mutuellement. Dans l’exemple précédent, le membre afficher mentionne les membres x et y, dont la déflnition se trouve après celle de afficher.
Jargon. On appelle
{ objet une donnée d’un type classe ou structure,
{ fonction membre un membre d’une classe qui est une fonction6, { donnée membre un membre qui est une variable7.
Accès aux membres
Accès aux membres d’un objet
On accède aux membres des objets en C++ comme on accède aux membres des structures en C. Par exemple, aµ la suite de la déflnition de la classe Point donnée précédemment on peut déclarer des variables de cette classe en écrivant8 :
Point a, b, *pt; // deux points et un pointeur de point
Dans un contexte oµu le droit de faire un tel accès est acquis (cf. section 2.2.3) l’accès aux membres du point a s’écrit :
a.x = 0; // un accès bien écrit au membre x du point a
d = a.distance(b); // un appel bien écrit de la fonction distance de l’objet a
Si on suppose que le pointeur pt a eté initialisé, par exemple par une expression telle que
pt = new Point; // allocation dynamique d’un point
alors des accès analogues aux précédents s’écrivent :
pt->x = 0; // un accès bien écrit au membre x du point pointé par pt
d = pt->distance(b); // un appel de la fonction distance de l’objet pointé par pt
A propos de l’accès µa un membre d’un objet, deux questions se posent. Il faut comprendre qu’elles sont tout aµ fait indépendantes l’une de l’autre :
{ l’accès est-il bien écrit ? c’est-µa-dire, désigne-t-il bien le membre voulu de l’objet voulu ?
{ cet accès est-il légitime ? c’est-µa-dire, dans le contexte oµu il est écrit, a-t-on le droit d’accès sur le membre en question ? La question des droits d’accès est traitée µa la section 2.2.3.
6Dans la plupart des langages orientés objets, les fonctions membres sont appelées méthodes.
7 Dans beaucoup de langages orientés objets, les données membres sont appelées variables d’instance et aussi, sous certaines conditions, propriétés
8Notez que, une fois la classe déclarée, il n’est pas obligatoire d’écrire class devant Point pour y faire référence.
Accès µa ses propres membres, accès µa soi-m^eme
Quand des membres d’un objet apparaissent dans une expression écrite dans une fonction du m^eme objet on dit que ce dernier fait un accès µa ses propres membres.
On a droit dans ce cas µa une notation simpliflée : on écrit le membre tout seul, sans expliciter l’objet en question. C’est ce que nous avons fait dans les fonctions de la classe Point :
class Point {
…
void afficher() {
cout << ’(’ << x << ’,’ << y << ’)’;
}
…
};
Dans la fonction afficher, les membres x et y dont il question sont ceux de l’objet µa travers lequel on aura appelé cette fonction. Autrement dit, lors d’un appel comme
unPoint.afficher();
le corps de cette fonction sera équivalent µa
cout << ’(’ << unPoint.x << ’,’ << unPoint.y << ’)’;
Accesµ aµ soi-meme^. Il arrive que dans une fonction membre d’un objet on doive faire référence µa l’objet (tout entier) µa travers lequel on a appelé la fonction. Il faut savoir que dans une fonction membre9 on dispose de la pseudo variable this qui représente un pointeur vers l’objet en question.
Par exemple, la fonction a–cher peut s’écrire de manière équivalente, mais cela n’a aucun intér^et :
void afficher() {
cout << ’(’ << this->x << ’,’ << this->y << ’)’;
}
Pour voir un exemple plus utile d’utilisation de this imaginons qu’on nous demande d’ajouter µa la classe Point deux fonctions booléennes, une pour dire si deux points sont égaux, une autre pour dire si deux points sont le m^eme objet. Dans les deux cas le deuxième point est donné par un pointeur :
class Point {
…
bool pointEgal(Point *pt) {
return pt->x == x && pt->y == y;
}
bool memePoint(Point *pt) {
return pt == this;
}
…
};
Membres publics et privés
Par défaut, les membres des classes sont privés. Les mots clés public et private permettent de modifler les droits d’accès des membres :
class nom {
les membres déclarés ici sont privés
public:
les membres déclarés ici sont publics
private:
les membres déclarés ici sont privés
etc.
};
Les expressions public: et private: peuvent appara^‡tre un nombre quelconque de fois dans une classe.
Les membres déclarés après private: (resp. public:) sont privés (resp. publics) jusqu’µa la fln de la classe, ou jusqu’µa la rencontre d’une expression public: (resp. private:).
Un membre public d’une classe peut ^etre accédé partout oµu il est visible ; un membre privé ne peut ^etre accédé que depuis une fonction membre de la classe (les notions de membre protégé, cf. section 4.2.1, et de classes et fonctions amies, cf. section 2.9, nuanceront cette a–rmation).
Si p est une expression de type Point :
{ dans une fonction qui n’est pas membre ou amie de la classe Point, les expressions p.x ou p.y pourtant syntaxiquement correctes et sans ambigu˜‡té, constituent des accès illégaux aux membres privés x et y de la classe Point,
{ les expressions p.afficher() ou p.placer(u, v) sont des accès légaux aux membres publics afficher et placer, qui se résolvent en des accès parfaitement légaux aux membres p.x et p.y.
Encapsulation au niveau de la classe
Les fonctions membres d’une classe ont le droit d’accéder µa tous les membres de la classe : deux objets de la m^eme classe ne peuvent rien se cacher. Par exemple, le programme suivant montre notre classe Point augmentée d’une fonction pour calculer la distance d’un point µa un autre :
class Point {
public:
void afficher() {
cout << ’(’ << x << ’,’ << y << ’)’;
}
void placer(int a, int b) {
validation des valeurs de a et b;
x = a; y = b;
}
double distance(Point autrePoint) {
int dx = x – autrePoint.x;
int dy = y – autrePoint.y;
return sqrt(dx * dx + dy * dy);
}
private:
int x, y;
};
Lors d’un appel tel que p.distance(q) l’objet p accède aux membres privés x et y de l’objet q. On dit que C++ pratique l’encapsulation au niveau de la classe, non au niveau de l’objet.
On notera au passage que, contrairement µa d’autres langages orientés objets, en C++ encapsuler n’est pas cacher mais interdire. Les usagers d’une classe voient les membres privés de cette dernière, mais ne peuvent pas les utiliser.
Structures
Une structure est la m^eme chose qu’une classe mais, par défaut, les membres y sont publics. Sauf pour ce qui touche cette question, tout ce qui sera dit dans la suite µa propos des classes s’appliquera donc aux structures :
struct nom {
les membres déclarés ici sont publics
private:
les membres déclarés ici sont privés
public:
les membres déclarés ici sont publics
etc.
};
Déflnition des classes
Déflnition séparée et opérateur de résolution de portée
Tous les membres d’une classe doivent ^etre au moins déclarés µa l’intérieur de la formule classnomf…g ; qui constitue la déclaration de la classe.
Cependant, dans le cas des fonctions, aussi bien publiques que privées, on peut se limiter µa n’écrire que leur en-t^ete µa l’intérieur de la classe et déflnir le corps ailleurs, plus loin dans le m^eme flchier ou bien dans un autre flchier.
Il faut alors un moyen pour indiquer qu’une déflnition de fonction, écrite en dehors de toute classe, est en réalité la déflnition d’une fonction membre d’une classe. Ce moyen est l’opérateur de résolution de portée, dont la syntaxe est
NomDeClasse::
Par exemple, voici notre classe Point avec la fonction distance déflnie séparément :
class Point {
public:
…
double distance(Point autrePoint);
…
}
Il faut alors, plus loin dans le m^eme flchier ou bien dans un autre flchier, donner la déflnition de la fonction
promise dans la classe Point. Cela s’écrit :
double Point::distance(Point autrePoint) {
int dx = x – autrePoint.x;
int dy = y – autrePoint.y;
return sqrt(dx * dx + dy * dy);
};
Déflnir les fonctions membres µa l’extérieur de la classe allège la déflnition de cette dernière et la rend plus compacte. Mais la question n’est pas qu’esthétique, il y a une difiérence de taille : les fonctions déflnies µa l’intérieur d’une classe sont implicitement qualiflées en ligne (cf. section 1.4).
Conséquence : la plupart des fonctions membres seront déflnies séparément. Seules les fonctions courtes, rapides et fréquemment appelées mériteront d’^etre déflnies dans la classe.
Fichier d’en-t^ete et flchier d’implémentation
En programmation orientée objets, programmer c’est déflnir des classes. Le plus souvent ces classes sont destinées µa ^etre utilisées dans plusieurs programmes, présents et µa venir10. Se pose alors la question : comment disposer le code d’une classe pour faciliter son utilisation ?
Voici comment on procède généralement :
{ les déflnitions des classes se trouvent dans des flchiers en-t^ete (flchiers .h , .hpp , etc.),
{ chacun des ces flchiers en-t^ete contient la déflnition d’une seule classe ou d’un groupe de classes intimement liées ; par exemple, la déflnition de notre classe Point pourrait constituer un flchier Point.h { les déflnitions des fonctions membres qui ne sont pas déflnies µa l’intérieur de leurs classes sont écrites dans des flchiers sources (flchiers .cpp ou .cp ), { aux programmeurs utilisateurs de ces classes sont distribués : { les flchiers .h { le flchiers objets résultant de la compilation des flchiers .cpp
Par exemple, voici les flchiers correspondants µa notre classe Point (toujours très modeste) :
Fichier Point.h :
class Point {
public:
void placer(int a, int b) {
validation de a et b
x = a; y = b;
}
double distance(Point autrePoint);
private:
int x, y;
};
Fichier Point.cpp :
#include « Point.h »
#include <math.h>
double Point::distance(Point autrePoint) {
int dx = x – autrePoint.x;
int dy = y – autrePoint.y;
return sqrt(dx * dx + dy * dy);
}
La compilation du flchier Point.cpp produira un flchier objet (nommé généralement Point.o ou Point.obj). Dans ces conditions, la distribution de la classe Point sera composée des deux flchiers Point.h et Point.obj, ce dernier ayant éventuellement eté transformé en un flchier bibliothèque (nommé alors Point.lib ou quelque chose comme »ca). Bien entendu, tout programme utilisateur de la classe Point devra comporter la directive
#include « Point.h »
et devra, une fois compilé, ^etre relié au flchier Point.obj ou Point.lib.
Constructeurs
Déflnition de constructeurs
Un constructeur d’une classe est une fonction membre spéciale qui :
{ a le m^eme nom que la classe,
{ n’indique pas de type de retour,
{ ne contient pas d’instruction return.
Le r^ole d’un constructeur est d’initialiser un objet, notamment en donnant des valeurs µa ses données membres. Le constructeur n’a pas µa s’occuper de trouver l’espace pour l’objet ; il est appelé (immédiatement) après que cet espace ait eté obtenu, et cela quelle que soit la sorte d’allocation qui a eté faite : statique, automatique ou dynamique, cela ne regarde pas le constructeur. Exemple :
class Point {
public:
Point(int a, int b) {
validation des valeurs de a et b
x = a; y = b;
}
… autres fonctions membres …
private:
int x, y;
};
Un constructeur de la classe est toujours appelé, explicitement (voir ci-dessous) ou implicitement, lorsqu’un objet de cette classe est créé, et en particulier chaque fois qu’une variable ayant cette classe pour type est déflnie.
C’est le couple déflnition de la variable + appel du constructeur qui constitue la réalisation en C++ du concept création d’un objet . L’intér^et pour le programmeur est évident : garantir que, dès leur introduction dans un programme, tous les objets sont garnis et cohérents, c’est-µa-dire éviter les variables indéflnies, au contenu incertain.
Une classe peut posséder plusieurs constructeurs, qui doivent alors avoir des signatures difiérentes :
class Point {
public:
Point(int a, int b) {
validation de a et b
x = a; y = b;
}
Point(int a) {
validation de a
x = a; y = 0;
}
Point() {
x = y = 0;
}
…
private:
int x, y;
};
L’emploi de paramètres avec des valeurs par défaut permet de grouper des constructeurs. La classe suivante possède les m^emes constructeurs que la précédente :
class Point {
public:
Point(int a = 0, int b = 0) {
validation de a et b
x = a; y = b;
}
…
private:
int x, y;
};
Comme les autres fonctions membres, les constructeurs peuvent ^etre déclarés dans la classe et déflnis ailleurs. Ainsi, la classe précédente pourrait s’écrire également
class Point {
public:
Point(int a = 0, int b = 0);
…
private:
int x, y;
};
et, ailleurs :
Point::Point(int a, int b) {
validation de a et b
x = a; y = b;
}
Deux remarques genéralesé. 1. Comme l’exemple ci-dessus le montre, lorsqu’une fonction fait l’objet d’une déclaration et d’une déflnition séparées, comme le constructeur Point, les éventuelles valeurs par défaut des argument concernent la déclaration, non la déflnition.
1 Elements prealables
1.1 Placement des declarations de variables
1.2 Booleens
1.3 References
1.3.1 Notion
1.3.2 References paramµetres des fonctions
1.3.3 Fonctions renvoyant une reference
1.3.4 References sur des donnees constantes
1.3.5 References ou pointeurs ?
1.4 Fonctions en ligne
1.5 Valeurs par d¶efaut des arguments des fonctions
1.6 Surcharge des noms de fonctions
1.7 Appel et d¶e¯nition de fonctions ¶ecrites en C
1.8 Entr¶ees-sorties simples
1.9 Allocation dynamique de m¶emoire
2 Classes
2.1 Classes et objets
2.2 Accµes aux membres
2.2.1 Accµes aux membres d’un objet
2.2.2 Accµes µa ses propres membres, accµes µa soi-m^eme
2.2.3 Membres publics et priv¶es
2.2.4 Encapsulation au niveau de la classe
2.2.5 Structures
2.3 D¶e¯nition des classes
2.3.1 D¶e¯nition s¶epar¶ee et op¶erateur de r¶esolution de port¶ee
2.3.2 Fichier d’en-t^ete et ¯chier d’impl¶ementation
2.4 Constructeurs
2.4.1 D¶e¯nition de constructeurs
2.4.2 Appel des constructeurs
2.4.3 Constructeur par d¶efaut
2.4.4 Constructeur par copie (clonage)
2.5 Construction des objets membres
2.6 Destructeurs
2.7 Membres constants
2.7.1 Donn¶ees membres constantes
2.7.2 Fonctions membres constantes
2.8 Membres statiques
2.8.1 Donn¶ees membres statiques
2.8.2 Fonctions membres statiques
2.9 Amis
2.9.1 Fonctions amies
2.9.2 Classes amies
3 Surcharge des op¶erateurs
3.1 Principe
3.1.1 Surcharge d’un op¶erateur par une fonction membre
3.1.2 Surcharge d’un op¶erateur par une fonction non membre
3.2 Quelques exemples
3.2.1 Injection et extraction de donn¶ees dans les °ux
3.2.2 A®ectation
3.3 Op¶erateurs de conversion
3.3.1 Conversion vers un type classe
3.3.2 Conversion d’un objet vers un type primitif
4 H¶eritage
4.1 Classes de base et classes d¶eriv¶ees
4.2 H¶eritage et accessibilit¶e des membres
4.2.1 Membres prot¶eg¶es
4.2.2 H¶eritage priv¶e, prot¶eg¶e, public
4.3 Red¶e¯nition des fonctions membres
4.4 Cr¶eation et destruction des objets d¶eriv¶es
4.5 R¶ecapitulation sur la cr¶eation et destruction des objets
4.5.1 Construction
4.5.2 Destruction
4.6 Polymorphisme
4.6.1 Conversion standard vers une classe de base
4.6.2 Type statique, type dynamique, g¶en¶eralisation
4.7 Fonctions virtuelles
4.8 Classes abstraites
4.9 Identi¯cation dynamique du type
4.9.1 L’operateur dynamic cast
4.9.2 L’operateur typeid
5 Modµeles (templates)
5.1 Modµeles de fonctions
5.2 Modµeles de classes
5.2.1 Fonctions membres d’un modµele
6 Exceptions
6.1 Principe et syntaxe
6.2 Attraper une exception
6.3 Declaration des exceptions qu’une fonction laisse echapper
6.4 La classe exception