Chargement...

Constructeur de copie et opérateur d’affectation en C++

Apprenez les constructeurs de copie et les opérateurs d’affectation en C++ avec des exemples de gestion des ressources.

En C++, lors de la copie ou de l’affectation d’objets, deux mécanismes spéciaux entrent en jeu : le Constructeur de copie (Copy Constructor) et l’Opérateur d’affectation (=). Tous deux servent à transférer des données entre objets, mais ils diffèrent par leur moment d’exécution et leur usage. Dans cet article, nous allons étudier leurs différences, leurs exemples d’utilisation et le concept de copie profonde (deep copy).


1. Qu’est-ce qu’un Constructeur de Copie ?

Le constructeur de copie est une méthode spéciale appelée lorsqu’un nouvel objet est créé à partir d’un objet existant, en en réalisant une copie.


class Book {
private:
    string *title;

public:
    Book(string t) {
        title = new string(t);
        cout << "Constructeur appelé." << endl;
    }

    // Constructeur de copie
    Book(const Book &other) {
        title = new string(*other.title); // Copie profonde
        cout << "Constructeur de copie appelé." << endl;
    }

    ~Book() {
        delete title;
        cout << "Destructeur appelé." << endl;
    }

    void Show() const { cout << "Livre : " << *title << endl; }
};

Utilisation :


int main() {
    Book b1("Programmation C++");
    Book b2 = b1; // Le constructeur de copie est appelé

    b1.Show();
    b2.Show();
}

Sortie :


Constructeur appelé.
Constructeur de copie appelé.
Livre : Programmation C++
Livre : Programmation C++
Destructeur appelé.
Destructeur appelé.

Dans ce cas, chaque objet possède sa propre zone mémoire (copie profonde). Si seule l’adresse était copiée (copie superficielle), les deux objets partageraient la même mémoire, ce qui entraînerait une erreur de « double suppression » lors de l’appel du destructeur.


2. Qu’est-ce que l’Opérateur d’Affectation (=) ?

L’opérateur d’affectation est utilisé pour copier les valeurs d’un objet vers un autre objet déjà existant. Cela signifie que l’objet existe déjà, mais que son contenu est remplacé.


class Book {
private:
    string *title;

public:
    Book(string t) {
        title = new string(t);
        cout << "Constructeur appelé." << endl;
    }

    // Opérateur d’affectation
    Book& operator=(const Book &other) {
        cout << "Opérateur d’affectation appelé." << endl;

        // Vérification d’auto-affectation
        if (this == &other)
            return *this;

        // Libérer l’ancienne mémoire
        delete title;

        // Créer une nouvelle copie
        title = new string(*other.title);

        return *this;
    }

    ~Book() {
        delete title;
        cout << "Destructeur appelé." << endl;
    }

    void Show() const { cout << "Livre : " << *title << endl; }
};

Utilisation :


int main() {
    Book b1("C++ Moderne");
    Book b2("Guide Python");

    b2 = b1; // L’opérateur d’affectation est appelé
    b1.Show();
    b2.Show();
}

Sortie :


Constructeur appelé.
Constructeur appelé.
Opérateur d’affectation appelé.
Livre : C++ Moderne
Livre : C++ Moderne
Destructeur appelé.
Destructeur appelé.

Ici, les deux objets ont des adresses mémoire différentes, mais des contenus identiques. Les anciennes données ont été correctement supprimées et remplacées par les nouvelles.


3. Problème de Copie Superficielle (Shallow Copy)

Si ni le constructeur de copie ni l’opérateur d’affectation ne sont définis, le compilateur C++ crée automatiquement une « copie superficielle (shallow copy) ». Dans ce cas, seule l’adresse est copiée et les données réelles sont partagées.


class ShallowBook {
public:
    string *title;

    ShallowBook(string t) {
        title = new string(t);
    }

    ~ShallowBook() {
        delete title;
    }
};

int main() {
    ShallowBook b1("C++ Primer");
    ShallowBook b2 = b1; // Copie superficielle automatique

    cout << *b1.title << endl;
    cout << *b2.title << endl; // Même zone mémoire !

} // Le destructeur supprime deux fois → plantage

Par conséquent, dans les classes utilisant la mémoire dynamique, le constructeur de copie et l’opérateur d’affectation doivent être définis manuellement.


4. Utilisation sûre avec la copie profonde (Deep Copy)

Une copie profonde duplique les données elles-mêmes dans une nouvelle zone mémoire. Ainsi, chaque objet gère sa propre mémoire de manière indépendante.


class DeepBook {
private:
    string *title;

public:
    DeepBook(string t) {
        title = new string(t);
    }

    // Constructeur de copie profonde
    DeepBook(const DeepBook &other) {
        title = new string(*other.title);
    }

    // Opérateur d’affectation avec copie profonde
    DeepBook& operator=(const DeepBook &other) {
        if (this == &other) return *this;
        delete title;
        title = new string(*other.title);
        return *this;
    }

    ~DeepBook() {
        delete title;
    }

    void Show() const { cout << *title << endl; }
};

int main() {
    DeepBook b1("J'apprends la copie profonde");
    DeepBook b2 = b1; // Constructeur de copie
    DeepBook b3("Vide");
    b3 = b1; // Opérateur d’affectation

    b1.Show();
    b2.Show();
    b3.Show();
}

Chaque objet gère sa propre mémoire ; le destructeur est appelé en toute sécurité pour chacun d’eux.


5. La Règle des Trois (Rule of Three)

Si une classe :

→ Alors les trois doivent être définis ensemble. Si l’un est défini mais pas les autres, le compilateur générera les versions manquantes automatiquement, ce qui peut provoquer des comportements inattendus.

Depuis C++11 : le concept de « Rule of Five » a été introduit, incluant le constructeur de déplacement (move constructor) et l’opérateur d’affectation par déplacement.


6. Exemple pratique : Copie et affectation d’un livre

L’exemple suivant montre comment le constructeur de copie et l’opérateur d’affectation fonctionnent ensemble.


#include <iostream>
#include <string>
using namespace std;

class Book {
private:
    string *title;

public:
    Book(string t) {
        title = new string(t);
        cout << "Constructeur : " << *title << endl;
    }

    // Constructeur de copie
    Book(const Book &other) {
        title = new string(*other.title);
        cout << "Constructeur de copie : " << *title << endl;
    }

    // Opérateur d’affectation
    Book& operator=(const Book &other) {
        cout << "Opérateur d’affectation : " << *other.title << endl;
        if (this == &other) return *this;
        delete title;
        title = new string(*other.title);
        return *this;
    }

    ~Book() {
        cout << "Destructeur : " << *title << endl;
        delete title;
    }

    void Show() const { cout << *title << endl; }
};

int main() {
    Book b1("Fondamentaux du C++");
    Book b2 = b1;      // Constructeur de copie
    Book b3("Temporaire");
    b3 = b1;           // Opérateur d’affectation

    cout << "----- Sortie -----" << endl;
    b1.Show();
    b2.Show();
    b3.Show();
}

Sortie :


Constructeur : Fondamentaux du C++
Constructeur de copie : Fondamentaux du C++
Constructeur : Temporaire
Opérateur d’affectation : Fondamentaux du C++
----- Sortie -----
Fondamentaux du C++
Fondamentaux du C++
Fondamentaux du C++
Destructeur : Fondamentaux du C++
Destructeur : Fondamentaux du C++
Destructeur : Fondamentaux du C++

Ainsi, la copie et l’affectation des objets sont effectuées de manière sûre et contrôlée.


7. TL;DR

  • Constructeur de copie → Crée un nouvel objet en copiant un objet existant.
  • Opérateur d’affectation → Affecte le contenu d’un objet existant à un autre.
  • Sans copie profonde, des erreurs de « double suppression » peuvent se produire.
  • Rule of Three → Le destructeur, le constructeur de copie et l’opérateur d’affectation doivent être définis ensemble.
  • Rule of Five (C++11) → Inclut aussi le constructeur et l’opérateur d’affectation par déplacement.
  • Tous les exemples peuvent être exécutés en toute sécurité sous Visual Studio 2022 ou GCC 11+.

Articles connexes