Menu
Coddy logo textTech

Surcharge de l'opérateur d'affectation

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

L'opérateur d'affectation par copie (=) est appelé lorsque vous affectez un objet existant à un autre. Contrairement au constructeur de recopie (qui crée un nouvel objet), l'opérateur d'affectation travaille avec un objet qui existe déjà et peut posséder des ressources qui doivent d'abord être nettoyées.

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    ~Buffer() { delete[] data; }
    
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {              // 1. Vérification de l'auto-affectation
            delete[] data;                  // 2. Nettoyage de la ressource existante
            size = other.size;              // 3. Copie des données
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;                       // 4. Retourner *this
    }
};

La vérification de l'auto-affectation (this != &other) est critique. Sans elle, b = b; supprimerait les données avant de tenter de les copier, provoquant un comportement indéfini.

L'opérateur retourne *this par référence pour permettre le chaînage comme a = b = c;. Cela correspond au comportement des types intégrés.

Pour les classes gérant des ressources, vous implémenterez souvent à la fois les opérateurs d'affectation par copie et par déplacement :

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;           // Nettoyer la ressource actuelle
        data = other.data;       // Voler la ressource
        size = other.size;
        other.data = nullptr;    // Laisser la source dans un état valide
        other.size = 0;
    }
    return *this;
}

L'opérateur d'affectation par déplacement transfère la propriété au lieu de copier, rendant les opérations comme vec[0] = createBuffer(); bien plus efficaces.

challenge icon

Défi

Facile

Construisons une classe DynamicString qui gère un tableau de caractères alloué dynamiquement et gère correctement l'affectation entre des objets existants. Il s'agit d'un scénario pratique où vous devez transférer en toute sécurité des données de chaîne d'un objet à un autre sans fuites de mémoire ni pointeurs pendants.

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

  • DynamicString.h : Définissez une classe DynamicString qui stocke une chaîne de caractères de style C en utilisant la mémoire dynamique. Votre classe doit gérer :
    • Un pointeur privé char* pour les données de la chaîne
    • Un size_t privé pour la longueur
    • Un constructeur qui prend un const char* et crée une copie profonde (deep copy)
    • Un destructeur qui libère correctement la mémoire allouée
    • Un opérateur d'affectation par copie qui effectue une copie profonde avec une protection contre l'auto-affectation
    • Un opérateur d'affectation par déplacement qui transfère la propriété de manière efficace
    • Une méthode c_str() qui retourne la chaîne interne (const)
    • Une méthode length() qui retourne la longueur de la chaîne (const)

    Votre opérateur d'affectation par copie doit suivre les quatre étapes essentielles : vérifier l'auto-affectation, nettoyer les ressources existantes, copier les nouvelles données et retourner *this. L'opérateur d'affectation par déplacement doit "voler" le pointeur de la source et laisser la source dans un état vide valide (nullptr avec une longueur de 0).

  • main.cpp : Lisez trois chaînes de caractères depuis l'entrée (chacune sur une ligne séparée). Créez trois objets DynamicString : str1 avec la première entrée, str2 avec la deuxième entrée, et str3 avec la troisième entrée.

    Démontrez vos opérateurs d'affectation :

    1. Affichez l'état initial des trois chaînes
    2. Utilisez l'affectation par copie : str1 = str2;
    3. Affichez str1 et str2 après l'affectation par copie (les deux doivent avoir le même contenu)
    4. Utilisez l'affectation par déplacement : str2 = std::move(str3);
    5. Affichez str2 après l'affectation par déplacement, ainsi que la longueur de str3 (elle devrait être 0 après avoir été déplacée)

    Format de sortie :

    Initial:
    str1: <value>
    str2: <value>
    str3: <value>
    After copy (str1 = str2):
    str1: <value>
    str2: <value>
    After move (str2 = std::move(str3)):
    str2: <value>
    str3 length: 0

Utilisez <cstring> pour strlen et strcpy. Marquez votre opérateur d'affectation par déplacement comme noexcept. Incluez <utility> dans main.cpp pour std::move. N'oubliez pas les gardes d'inclusion (header guards) dans votre fichier d'en-tête.

Aide-mémoire

L'opérateur d'affectation par copie (=) affecte un objet existant à un autre. Il doit gérer le nettoyage des ressources existantes avant de copier les nouvelles données.

Structure de l'opérateur d'affectation par copie

ClassName& operator=(const ClassName& other) {
    if (this != &other) {              // 1. Vérification de l'auto-affectation
        // 2. Nettoyer les ressources existantes
        // 3. Copier les données depuis other
    }
    return *this;                       // 4. Retourner *this
}

Exemple avec mémoire dynamique

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            delete[] data;                  // Nettoyer la ressource existante
            size = other.size;
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;
    }
};

La vérification de l'auto-affectation (this != &other) empêche la suppression des données avant de les copier dans des cas comme b = b;.

Le retour de *this par référence permet le chaînage : a = b = c;.

Opérateur d'affectation par déplacement

L'opérateur d'affectation par déplacement transfère la propriété au lieu de copier, ce qui améliore l'efficacité :

ClassName& operator=(ClassName&& other) noexcept {
    if (this != &other) {
        // Nettoyer la ressource actuelle
        // Voler la ressource de other
        // Laisser other dans un état valide (nullptr, 0, etc.)
    }
    return *this;
}

Exemple d'affectation par déplacement

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;
        data = other.data;       // Voler la ressource
        size = other.size;
        other.data = nullptr;    // Laisser la source valide
        other.size = 0;
    }
    return *this;
}

Marquez l'affectation par déplacement comme noexcept pour une performance optimale avec les conteneurs standards.

Essayez vous-même

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

using namespace std;

int main() {
    // Lire trois chaînes de caractères depuis l'entrée
    string input1, input2, input3;
    getline(cin, input1);
    getline(cin, input2);
    getline(cin, input3);

    // Créer trois objets DynamicString
    DynamicString str1(input1.c_str());
    DynamicString str2(input2.c_str());
    DynamicString str3(input3.c_str());

    // TODO: Afficher l'état initial des trois chaînes
    // Format :
    // Initial :
    // str1: <value>
    // str2: <value>
    // str3: <value>

    // TODO: Utiliser l'affectation par copie : str1 = str2;

    // TODO: Afficher str1 et str2 après l'affectation par copie
    // Format :
    // After copy (str1 = str2):
    // str1: <value>
    // str2: <value>

    // TODO: Utiliser l'affectation par déplacement : str2 = std::move(str3);

    // TODO: Afficher str2 après le déplacement, et la longueur de str3
    // Format :
    // After move (str2 = std::move(str3)):
    // str2: <value>
    // str3 length: 0

    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