Exploration Approfondie des Classes C++ : Concepts et Mise en Pratique

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() ou accelerer().
  • 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, private et protected pour contrôler la visibilité et l'accessibilité des membres de la classe depuis l'extérieur de celle-ci. Les membres public sont accessibles de partout, tandis que les membres private ne 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;
}

Étiquettes: C++ classes ProgrammationOrientéeObjet composition StructuresC

Publié le 3 septembre à 06h49