Menu
Coddy logo textTech

Règle de trois / cinq / zéro

Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 25 sur 104.

Lorsque votre classe gère des ressources telles que la mémoire dynamique, vous avez appris que vous avez besoin d'un destructeur, d'un constructeur de copie et d'un constructeur de déplacement personnalisés. Mais il existe un principe directeur qui vous aide à décider quelles fonctions membres spéciales implémenter : la Règle de Trois, Cinq et Zéro.

La Règle des trois stipule : si vous définissez l'un de ces trois éléments, vous devriez définir les trois :

  • Destructeur
  • Constructeur de recopie
  • Opérateur d'affectation par copie

La Règle de Cinq étend cela pour le C++ moderne, en ajoutant des opérations de déplacement :

  • Destructeur
  • Constructeur par copie
  • Opérateur d'affectation par copie
  • Constructeur par déplacement
  • Opérateur d'affectation par déplacement
class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    ~Buffer() { delete[] data; }                          // 1. Destructeur
    Buffer(const Buffer& other);                          // 2. Constructeur par copie
    Buffer& operator=(const Buffer& other);               // 3. Affectation par copie
    Buffer(Buffer&& other) noexcept;                      // 4. Constructeur par déplacement
    Buffer& operator=(Buffer&& other) noexcept;           // 5. Affectation par déplacement
};

La Règle de Zéro est l'approche la plus simple : si votre classe ne gère pas directement de ressources, ne définissez aucune de ces fonctions. Laissez le compilateur les générer, ou utilisez des pointeurs intelligents et des conteneurs standards qui gèrent les ressources pour vous.

class Player {
    std::string name;              // std::string gère sa propre mémoire
    std::vector<int> scores;       // std::vector gère ses ressources
public:
    Player(std::string n) : name(n) {}
    // Aucun destructeur, fonction de copie ou de déplacement n'est nécessaire !
};

Le respect de ces règles permet d'éviter des bogues tels que la double libération, les fuites de mémoire et les pointeurs pendants qui surviennent lorsque certaines opérations sont définies mais que d'autres sont manquantes.

challenge icon

Défi

Facile

Construisons une classe TextBuffer qui suit la Règle de Cinq — en implémentant les cinq fonctions membres spéciales pour gérer correctement les données de caractères allouées dynamiquement. Cela démontrera comment les opérations de copie et de déplacement fonctionnent ensemble pour créer une classe robuste de gestion de ressources.

Vous allez créer deux fichiers pour organiser votre code :

  • TextBuffer.h : Définissez une classe TextBuffer qui stocke du texte dans un tableau de caractères alloué dynamiquement. Votre classe a besoin de :
    • Membres privés : un pointeur char* appelé data pour le contenu textuel, et un size_t length pour la longueur de la chaîne (sans inclure le terminateur nul)
    • Un constructeur paramétré qui prend une chaîne C (const char*), alloue de la mémoire, copie le contenu et affiche "TextBuffer created: <text>"
    • Un destructeur qui libère la mémoire (si elle n'est pas nulle) et affiche "TextBuffer destroyed"
    • Un constructeur de copie qui effectue une copie profonde et affiche "TextBuffer copied"
    • Un opérateur d'affectation par copie qui gère l'auto-affectation, nettoie les données existantes, effectue une copie profonde et affiche "TextBuffer copy-assigned". Retournez *this
    • Un constructeur de déplacement (marqué noexcept) qui transfère la propriété et affiche "TextBuffer moved". Laissez la source dans un état vide valide
    • Un opérateur d'affectation par déplacement (marqué noexcept) qui gère l'auto-affectation, nettoie les données existantes, transfère la propriété et affiche "TextBuffer move-assigned". Retournez *this
    • Une méthode getText() qui retourne le texte stocké (retourne une chaîne vide "" si data est nul)
    • Une méthode getLength() qui retourne la longueur
  • main.cpp : Démontrez les cinq fonctions membres spéciales en action. Lisez une chaîne de texte depuis l'entrée, puis :
    • Créez un TextBuffer appelé original avec le texte d'entrée
    • Créez copied en utilisant le constructeur de copie à partir de original
    • Créez another avec le texte "Temporary"
    • Utilisez l'affectation par copie : another = original
    • Créez moved par construction par déplacement à partir de original en utilisant std::move()
    • Créez target avec le texte "Target"
    • Utilisez l'affectation par déplacement : target = std::move(copied)
    • Affichez "--- Final State ---"
    • Affichez "original: <text> (length: <len>)" pour chaque tampon : original, copied, moved, another, target

Après les déplacements, les objets sources (original et copied) doivent afficher un texte vide avec une longueur de 0, tandis que les objets de destination détiennent les données transférées. Cela démontre la Règle de Cinq en action — les cinq fonctions travaillant ensemble pour assurer une gestion sûre des ressources.

Incluez <cstring> pour les fonctions de chaîne comme strlen et strcpy, et <utility> pour std::move().

Aide-mémoire

La Règle de Trois stipule que si vous définissez l'une de ces trois fonctions membres spéciales, vous devriez définir les trois :

  • Destructeur
  • Constructeur de copie
  • Opérateur d'affectation par copie

La Règle de Cinq étend cela pour le C++ moderne en ajoutant des opérations de déplacement :

  • Destructeur
  • Constructeur de copie
  • Opérateur d'affectation par copie
  • Constructeur de déplacement
  • Opérateur d'affectation par déplacement
class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    ~Buffer() { delete[] data; }                          // 1. Destructeur
    Buffer(const Buffer& other);                          // 2. Constructeur de copie
    Buffer& operator=(const Buffer& other);               // 3. Affectation par copie
    Buffer(Buffer&& other) noexcept;                      // 4. Constructeur de déplacement
    Buffer& operator=(Buffer&& other) noexcept;           // 5. Affectation par déplacement
};

La Règle de Zéro stipule que si votre classe ne gère pas directement de ressources, ne définissez aucune fonction membre spéciale. Utilisez des pointeurs intelligents et des conteneurs standards qui gèrent les ressources automatiquement :

class Player {
    std::string name;              // std::string gère sa propre mémoire
    std::vector<int> scores;       // std::vector gère ses ressources
public:
    Player(std::string n) : name(n) {}
    // Aucun destructeur, fonction de copie ou de déplacement n'est nécessaire !
};

Le respect de ces règles permet d'éviter des bogues tels que la double suppression, les fuites de mémoire et les pointeurs pendants.

Essayez vous-même

#include <iostream>
#include <string>
#include <utility>
#include "TextBuffer.h"

using namespace std;

int main() {
    string input;
    getline(cin, input);

    // TODO: Créer un TextBuffer nommé 'original' avec le texte d'entrée

    // TODO: Créer 'copied' en utilisant le constructeur de copie à partir de 'original'

    // TODO: Créer 'another' avec le texte "Temporary"

    // TODO: Utiliser l'affectation par copie : another = original

    // TODO: Créer 'moved' par construction par déplacement à partir de 'original' en utilisant std::move()

    // TODO: Créer 'target' avec le texte "Target"

    // TODO: Utiliser l'affectation par déplacement : target = std::move(copied)

    // TODO: Afficher "--- Final State ---"

    // TODO: Afficher l'état de chaque tampon dans ce format :
    // "original: <text> (length: <len>)"
    // Afficher pour : original, copied, moved, another, target

    return 0;
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet