Programmation Orientée Objet en C++ : Composition, Gestion de la Mémoire et Sémantique de Copie

L'utilisation de la composition permet de construire des objets complexes à partir d'objets plus simples. Dans cet exemple, une classe représentant une fenêtre contient une liste dynamique de boutons.

Composant Bouton (WidgetBouton.hpp)

#pragma once
#include <iostream>
#include <string>

class WidgetBouton {
public:
   WidgetBouton(const std::string& texte) : label(texte) {}
   
   const std::string& lireLabel() const { return label; }
   
   void declencher() const {
       std::cout << "[Action] Bouton '" << label << "' active." << std::endl;
   }

private:
   std::string label;
};

Gestionnaire de Fenêtre (CadreFenetre.hpp)

#pragma once
#include <vector>
#include <string>
#include <iostream>
#include "WidgetBouton.hpp"

class CadreFenetre {
public:
   CadreFenetre(const std::string& nom) : titre(nom) {
       elements.emplace_back("Quitter");
   }

   void ajouterBouton(const std::string& libelle) {
       for (const auto& b : elements) {
           if (b.lireLabel() == libelle) {
               std::cout << "Erreur : Le bouton " << libelle << " existe deja.\n";
               return;
           }
       }
       elements.emplace_back(libelle);
   }

   void rendu() const {
       std::string separateur(30, '-');
       std::cout << separateur << "\nFenetre : " << titre << "\n";
       for (size_t i = 0; i < elements.size(); ++i) {
           std::cout << i + 1 << ". " << elements[i].lireLabel() << "\n";
       }
       std::cout << separateur << std::endl;
   }

   void cliquer(const std::string& libelle) {
       for (auto& b : elements) {
           if (b.lireLabel() == libelle) {
               b.declencher();
               return;
           }
       }
       std::cout << "Bouton introuvable : " << libelle << std::endl;
   }

private:
   std::string titre;
   std::vector<WidgetBouton> elements;
};

Le code suivant démontre comment le conteneur std::vector de la bibliothèque standard gère la copie prfoonde, assurant que les modifications sur une instance n'affectent pas sa copie.

#include <iostream>
#include <vector>

void demoCopie() {
   std::vector<int> source = {10, 20, 30};
   std::vector<int> copie = source; // Copie profonde

   source[0] = 999;

   std::cout << "Source[0]: " << source[0] << " (attendu: 999)\n";
   std::cout << "Copie[0] : " << copie[0] << " (attendu: 10)\n";
}

void demoMatriceVector() {
   std::vector<std::vector<int>> grille = {{1, 2}, {3, 4}};
   auto duplication = grille;

   grille[0].push_back(5);

   std::cout << "Taille grille[0]: " << grille[0].size() << "\n";
   std::cout << "Taille duplication[0]: " << duplication[0].size() << "\n";
}

Pour comprendre les mécanismes internes de C++, nous implémentons un gestionnaire de tableau dynamique respectant la règle des trois (constructeur de copie, destructeur, opérateur d'affectation).

#pragma once
#include <iostream>
#include <algorithm>

class TableauEntier {
public:
   TableauEntier(int taille) : n(taille), donnees(new int[taille]()) {}
   
   // Constructeur de copie (Deep Copy)
   TableauEntier(const TableauEntier& autre) : n(autre.n), donnees(new int[autre.n]) {
       std::copy(autre.donnees, autre.donnees + n, donnees);
   }

   ~TableauEntier() { delete[] donnees; }

   TableauEntier& operator=(const TableauEntier& autre) {
       if (this != &autre) {
           int* nouveau = new int[autre.n];
           std::copy(autre.donnees, autre.donnees + autre.n, nouveau);
           delete[] donnees;
           donnees = nouveau;
           n = autre.n;
       }
       return *this;
   }

   int& acceder(int index) {
       if (index < 0 || index >= n) exit(1);
       return donnees[index];
   }

   int taille() const { return n; }

private:
   int n;
   int* donnees;
};

Cette classe encapsule un tableau unidimensionnel pour simuler une structure bidimensionnelle, optimisant ainsi l'alocation mémoire.

#include <iostream>
#include <cstring>

class Matrice {
public:
   Matrice(int l, int c, double val = 0.0) : lignes(l), colonnes(c) {
       data = new double[l * c];
       for (int i = 0; i < l * c; ++i) data[i] = val;
   }

   Matrice(const Matrice& m) : lignes(m.lignes), colonnes(m.colonnes) {
       data = new double[lignes * colonnes];
       std::memcpy(data, m.data, lignes * colonnes * sizeof(double));
   }

   ~Matrice() { delete[] data; }

   double& at(int i, int j) {
       return data[i * colonnes + j];
   }

   void afficher() const {
       for (int i = 0; i < lignes; ++i) {
           for (int j = 0; j < colonnes; ++j) {
               std::cout << data[i * colonnes + j] << " ";
           }
           std::cout << "\n";
       }
   }

private:
   int lignes, colonnes;
   double* data;
};

L'exemple suivant combine std::vector et l'algorithme std::sort avec une expression lambda pour maintenir une liste de contacts ordonnée.

#include <string>
#include <vector>
#include <algorithm>
#include <iostream>

struct Fiche {
   std::string nom;
   std::string tel;
};

class Carnet {
public:
   void inserer(const std::string& n, const std::string& t) {
       liste.push_back({n, t});
       ordonner();
   }

   void supprimer(const std::string& n) {
       auto it = std::remove_if(liste.begin(), liste.end(), [&](const Fiche& f) {
           return f.nom == n;
       });
       liste.erase(it, liste.end());
   }

   void lister() const {
       for (const auto& f : liste) {
           std::cout << f.nom << " : " << f.tel << "\n";
       }
   }

private:
   std::vector<Fiche> liste;
   void ordonner() {
       std::sort(liste.begin(), liste.end(), [](const Fiche& a, const Fiche& b) {
           return a.nom < b.nom;
       });
   }
};

Étiquettes: C++ Programmation orientée objet Gestion Mémoire STL algorithmique

Publié le 29 octobre à 08h39