Les classes en C++ représentent une pierre angulaire de la programmation orientée objet (POO), offrant un mécanisme puissant pour structurer le code et modéliser des entités du monde réel. Elles permettent de créer des types de données personnalisés qui encapsulent à la fois des attributs (données) et des comportements (fonctions) au sein d'une seule unité. L'objectif prinicpal est de lier les données à leurs opérations associées, améliorant ainsi la modularité, la réutilisabilité et la maintenabilité du code.
Introduction aux Classes C++
Au cœur de la conception d'une classe, on retrouve plusieurs éléments fondamentaux :
- Membres de données (Attributs) : Ce sont les variables déclarées au sein de la classe, qui définissent les propriétés ou l'état d'un objet. Par exemple, une classe "Voiture" pourrait avoir des attributs comme la couleur, la marque ou le modèle.
- Fonctions membres (Méthodes) : Ce sont les fonctions définies dans la classe, qui représentent les actions ou les opérations qu'un objet peut effectuer. Pour notre "Voiture", des méthodes pourraient être
demarrer(),arreter()ouaccelerer(). - Constructeurs et destructeurs : Le constructeur est une fonction membre spéciale qui est automatiquement appelée lors de la création d'un objet. Son rôle est d'initialiser l'objet. Le destructeur est une autre fonction membre spéciale, appelée automatiquement lorsque l'objet est détruit, pour effectuer des opérations de nettoyage (libération de mémoire, fermeture de fichiers, etc.).
- Spécificateurs d'accès : C++ utilise des mots-clés comme
public,privateetprotectedpour contrôler la visibilité et l'accessibilité des membres de la classe depuis l'extérieur de celle-ci. Les membrespublicsont accessibles de partout, tandis que les membresprivatene sont accessibles que depuis l'intérieur de la classe, favorisant ainsi l'encapsulation. - Héritage : Il s'agit d'un mécanisme permettant à une classe (classe dérivée) d'acquérir les propriétés et les comportements d'une autre classe (classe de base), favorisant la réutilisation du code et l'établissement de relations hiérarhciques.
Grâce à ces concepts, les classes C++ offrent une structrue robuste pour concevoir des systèmes complexes, rendant le code plus organisé et plus facile à gérer.
De la Structure C à la Classe C++
Structures C : Un Prélude
Avant d'explorer pleinement les classes C++, il est utile de se remémorer comment des concepts similaires étaient abordés en C avec les structures. En C, une structure peut regrouper des données de différents types, et des pointeurs de fonction peuvent être utilisés pour simuler des comportements. Cependant, la gestion des données et des fonctions reste dissociée, sans l'encapsulation offerte par les classes.
#include <stdio.h>
#include <stdlib.h> // Pour malloc et free
#include <string.h> // Pour strcpy, si char* est utilisé pour les chaînes
// Définition de la structure Vehicule
typedef struct Vehicule {
char couleur[20];
char marque[50];
char modele[50];
int anneeFabrication;
// Pointeur de fonction pour afficher les détails du véhicule
void (*afficherDetails)(struct Vehicule*);
// Pointeur de fonction pour démarrer le véhicule
void (*demarrer)(struct Vehicule*);
} Vehicule;
// Implémentation de la fonction d'affichage pour un véhicule
void afficherDetailsVehicule(Vehicule* v) {
if (v == NULL) return;
fprintf(stdout, "Détails du véhicule: Marque: %s, Modèle: %s, Couleur: %s, Année: %d\n",
v->marque, v->modele, v->couleur, v->anneeFabrication);
}
// Implémentation de la fonction de démarrage pour un véhicule
void demarrerVehicule(Vehicule* v) {
if (v == NULL) return;
fprintf(stdout, "%s %s démarre son moteur.\n", v->marque, v->modele);
}
int main() {
// Création d'une instance de Vehicule sur la pile
Vehicule maCitadine;
strcpy(maCitadine.couleur, "Rouge");
strcpy(maCitadine.marque, "Peugeot");
strcpy(maCitadine.modele, "208");
maCitadine.anneeFabrication = 2019;
maCitadine.afficherDetails = afficherDetailsVehicule;
maCitadine.demarrer = demarrerVehicule;
maCitadine.afficherDetails(&maCitadine);
maCitadine.demarrer(&maCitadine);
fprintf(stdout, "\n");
// Création d'une instance de Vehicule sur le tas (mémoire dynamique)
Vehicule* monSUV = (Vehicule*)malloc(sizeof(Vehicule));
if (monSUV == NULL) {
fprintf(stderr, "Erreur d'allocation mémoire pour monSUV.\n");
return 1;
}
strcpy(monSUV->couleur, "Bleu Nuit");
strcpy(monSUV->marque, "BMW");
strcpy(monSUV->modele, "X5");
monSUV->anneeFabrication = 2021;
monSUV->afficherDetails = afficherDetailsVehicule;
monSUV->demarrer = demarrerVehicule;
monSUV->afficherDetails(monSUV);
monSUV->demarrer(monSUV);
free(monSUV); // Libérer la mémoire allouée
monSUV = NULL;
return 0;
}
Introduction des Classes C++ (approche initiale)
En C++, l'introduction du mot-clé class ou même l'utilisation d'une struct (qui en C++ est fondamentalement une classe avec des membres par défaut publics) nous rapproche d'un modèle objet. Nous pouvons remplacer les tableaux de caractères par std::string pour une gestion plus aisée des chaînes, et toujours utiliser des pointeurs de fonction pour les méthodes, bien que ce ne soit pas la manière idiomatique en C++.
#include <iostream>
#include <string>
#include <memory> // Pour std::unique_ptr ou std::shared_ptr, ou pour new/delete
// Utilisation d'un namespace pour simplifier l'accès aux fonctionnalités standards
using namespace std;
// Définition de la classe Vehicule (ici, c'est encore une classe avec pointeurs de fonction)
class Vehicule {
public: // Les membres sont publics pour un accès facile, comme une struct C
string couleur;
string marque;
string modele;
int anneeFabrication;
// Pointeur de fonction pour afficher les informations
void (*ptrAfficherInfos)(const string&, const string&, const string&, int);
// Pointeur de fonction pour une action de conduite
void (*ptrConduire)(const string&);
};
// Implémentation d'une fonction d'affichage globale
void afficherInfosVehiculeGlobal(const string& couleur, const string& marque, const string& modele, int annee) {
cout << "Informations sur le véhicule:" << endl;
cout << " Marque: " << marque << ", Modèle: " << modele << endl;
cout << " Couleur: " << couleur << ", Année: " << annee << endl;
}
// Implémentation d'une fonction de conduite globale
void conduireVehiculeGlobal(const string& modele) {
cout << "Le " << modele << " est en mouvement!" << endl;
}
int main() {
// Création d'un objet Vehicule sur la pile
Vehicule berline;
berline.couleur = "Argent";
berline.marque = "Mercedes-Benz";
berline.modele = "Classe C";
berline.anneeFabrication = 2020;
berline.ptrAfficherInfos = afficherInfosVehiculeGlobal;
berline.ptrConduire = conduireVehiculeGlobal;
berline.ptrAfficherInfos(berline.couleur, berline.marque, berline.modele, berline.anneeFabrication);
berline.ptrConduire(berline.modele);
cout << endl;
// Création d'un objet Vehicule sur le tas (mémoire dynamique)
// std::unique_ptr est préférable pour la gestion automatique de la mémoire
auto camionnette = make_unique<Vehicule>(); // Ou new Vehicule(); si pas de smart pointer
camionnette->couleur = "Gris";
camionnette->marque = "Ford";
camionnette->modele = "F-150";
camionnette->anneeFabrication = 2022;
camionnette->ptrAfficherInfos = afficherInfosVehiculeGlobal;
camionnette->ptrConduire = conduireVehiculeGlobal;
camionnette->ptrAfficherInfos(camionnette->couleur, camionnette->marque, camionnette->modele, camionnette->anneeFabrication);
camionnette->ptrConduire(camionnette->modele);
// Pas besoin de delete avec unique_ptr, la mémoire est libérée automatiquement
// Si 'new Vehicule()' avait été utilisé, il faudrait 'delete camionnette;' ici.
return 0;
}
Les Méthodes Membres Authentiques
L'une des plus grandes avancées des classes C++ par rapport aux structures C est la capacité d'intégrer des fonctions directement comme membres de la classe. Ces fonctions membres ont un accès privilégié aux données membres de l'objet sur lequel elles opèrent, permettant une véritable encapsulation. De plus, l'introduction des constructeurs simplifie grandement l'initialisation des objets.
#include <iostream>
#include <string>
#include <memory> // Pour std::unique_ptr
using namespace std;
// Définition de la classe Vehicule avec de vraies fonctions membres
class Vehicule {
private: // Membres de données généralement privés pour l'encapsulation
string m_couleur;
string m_marque;
string m_modele;
int m_anneeFabrication;
public:
// Constructeur : appelé lors de la création de l'objet pour l'initialiser
Vehicule(const string& couleur, const string& marque, const string& modele, int annee)
: m_couleur(couleur), m_marque(marque), m_modele(modele), m_anneeFabrication(annee) {
cout << "Constructeur de Vehicule pour " << m_marque << " " << m_modele << " appelé." << endl;
}
// Destructeur : appelé lors de la destruction de l'objet pour le nettoyage
~Vehicule() {
cout << "Destructeur de Vehicule pour " << m_marque << " " << m_modele << " appelé." << endl;
}
// Fonction membre pour afficher les informations du véhicule
// 'const' indique que la fonction ne modifie pas l'état de l'objet
void afficherInfos() const {
cout << "Véhicule: " << m_marque << " " << m_modele << endl;
cout << " Couleur: " << m_couleur << ", Année: " << m_anneeFabrication << endl;
}
// Fonction membre pour simuler le démarrage du moteur
void demarrerMoteur() const {
cout << m_marque << " " << m_modele << " démarre son moteur. Vroum!" << endl;
}
// Exemple de getter pour accéder à un membre privé
string getModele() const {
return m_modele;
}
};
int main() {
// Création d'un objet Vehicule sur la pile, le constructeur est appelé
Vehicule maMoto("Noire", "Harley-Davidson", "Street Glide", 2023);
maMoto.afficherInfos();
maMoto.demarrerMoteur();
cout << endl;
// Création d'un objet Vehicule sur le tas avec un pointeur intelligent
auto monCamion = make_unique<Vehicule>("Blanc", "Volvo", "FH16", 2022);
monCamion->afficherInfos();
monCamion->demarrerMoteur();
cout << "Modèle du camion: " << monCamion->getModele() << endl;
// À la fin de main(), les destructeurs sont appelés pour maMoto et monCamion.
return 0;
}
La Composition d'Objets en C++
La composition est un principe de conception en POO où une classe contient des objets d'autres classes en tant que membres de données. Cela représente une relation "a un" (has-a), où un objet est "composé de" ou "contient" d'autres objets. C'est un moyen puissant de construire des systèmes complexes à partir de composants plus petits et gérables, favorisant la réutilisabilité et la modularité.
Par exemple, une classe Vehicule pourrait être composée d'objets Roue. Chaque instance de Vehicule "aura" des instances de Roue. La durée de vie de ces composants peut être gérée directement par l'objet conteneur (composition forte) ou indépendamment (agrégation).
#include <iostream>
#include <string>
#include <memory> // Pour std::unique_ptr
using namespace std;
// Classe représentant une roue
class Roue {
private:
string m_fabricant;
int m_diametrePouces; // Diamètre en pouces
public:
// Constructeur de Roue
Roue(const string& fabricant, int diametre)
: m_fabricant(fabricant), m_diametrePouces(diametre) {
cout << " Constructeur de Roue pour " << m_fabricant << " (" << m_diametrePouces << " pouces) appelé." << endl;
}
// Destructeur de Roue
~Roue() {
cout << " Destructeur de Roue pour " << m_fabricant << " appelé." << endl;
}
// Affiche les détails de la roue
void afficherDetailsRoue() const {
cout << " Roue - Fabricant: " << m_fabricant << ", Diamètre: " << m_diametrePouces << " pouces." << endl;
}
string getFabricant() const { return m_fabricant; }
};
// Classe représentant un véhicule, composant des objets Roue
class Vehicule {
private:
string m_couleur;
string m_marque;
string m_modele;
int m_anneeFabrication;
Roue m_roueDeSecours; // Composition directe (objet membre), gérée par le constructeur/destructeur de Vehicule
Roue* m_roueAvantDroite; // Composition par pointeur (objet alloué dynamiquement), gérée manuellement
public:
// Constructeur de Vehicule prenant des objets Roue pour la composition
Vehicule(const string& couleur, const string& marque, const string& modele, int annee,
const Roue& roueSecoursRef, Roue* roueAvantDroitePtr)
: m_couleur(couleur), m_marque(marque), m_modele(modele), m_anneeFabrication(annee),
m_roueDeSecours(roueSecoursRef), // Initialisation de m_roueDeSecours par copie
m_roueAvantDroite(roueAvantDroitePtr) // Initialisation de m_roueAvantDroite avec le pointeur fourni
{
cout << "Constructeur de Véhicule pour " << m_marque << " " << m_modele << " appelé." << endl;
}
// Destructeur de Vehicule : s'assure de libérer la mémoire allouée dynamiquement
~Vehicule() {
cout << "Destructeur de Véhicule pour " << m_marque << " " << m_modele << " appelé." << endl;
delete m_roueAvantDroite; // Libère la Roue allouée dynamiquement
m_roueAvantDroite = nullptr; // Évite les pointeurs "pendants"
}
// Affiche les informations générales du véhicule
void afficherInfos() const {
cout << "--- Détails du Véhicule ---" << endl;
cout << "Véhicule: " << m_marque << " " << m_modele << endl
<< " Couleur: " << m_couleur << ", Année: " << m_anneeFabrication << endl;
}
// Simule le démarrage du moteur
void demarrerMoteur() const {
cout << m_marque << " " << m_modele << " démarre son moteur. Vroum!" << endl;
}
// Inspecte et affiche les détails des roues composées
void inspecterRoues() const {
cout << "Inspection des roues du " << m_marque << " " << m_modele << ":" << endl;
cout << " Roue de secours: ";
m_roueDeSecours.afficherDetailsRoue(); // Accès à la méthode du membre composé
if (m_roueAvantDroite) {
cout << " Roue avant droite: ";
m_roueAvantDroite->afficherDetailsRoue(); // Accès à la méthode du membre composé via pointeur
} else {
cout << " Roue avant droite manquante ou non initialisée." << endl;
}
}
};
int main() {
// Création d'objets Roue qui seront composés dans Vehicule
Roue pneuVille("Michelin", 16); // Roue allouée sur la pile
Roue* pneuSport = new Roue("Pirelli", 18); // Roue allouée dynamiquement
cout << "\n--- Création de 'maVoiture' (sur la pile) ---" << endl;
Vehicule maVoiture("Bleu", "Renault", "Clio", 2021, pneuVille, pneuSport);
maVoiture.afficherInfos();
maVoiture.inspecterRoues();
maVoiture.demarrerMoteur();
cout << "\n--- Création de 'monBus' (sur le tas avec unique_ptr) ---" << endl;
// Création de Roue temporaires ou dynamiques pour le constructeur du bus
auto monBus = make_unique<Vehicule>(
"Jaune", "Mercedes", "Citaro", 2018,
Roue("Continental", 20), // Une Roue temporaire, copiée dans m_roueDeSecours
new Roue("Goodyear", 20) // Une Roue allouée dynamiquement
);
monBus->afficherInfos();
monBus->inspecterRoues();
monBus->demarrerMoteur();
cout << "\n--- Fin du programme ---" << endl;
// Les destructeurs sont appelés automatiquement :
// - Pour monBus (libère la mémoire de l'objet Vehicule et de sa Roue Goodyear)
// - Pour maVoiture (libère la Roue Pirelli qu'elle contenait)
// - Pour pneuSport (jamais delete car il a été transféré à maVoiture)
// - Pour pneuVille (libérée à la fin de sa portée)
// Note: pneuSport est une variable locale qui pointe vers la Roue qui a été assignée à maVoiture.
// La Roue elle-même sera nettoyée par le destructeur de maVoiture.
// Il n'est PAS correct de faire 'delete pneuSport;' ici car la propriété a été transférée.
return 0;
}