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.
Défi
FacileConstruisons 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 classeDynamicStringqui 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_tprivé 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).- Un pointeur privé
main.cpp: Lisez trois chaînes de caractères depuis l'entrée (chacune sur une ligne séparée). Créez trois objetsDynamicString:str1avec la première entrée,str2avec la deuxième entrée, etstr3avec la troisième entrée.Démontrez vos opérateurs d'affectation :
- Affichez l'état initial des trois chaînes
- Utilisez l'affectation par copie :
str1 = str2; - Affichez str1 et str2 après l'affectation par copie (les deux doivent avoir le même contenu)
- Utilisez l'affectation par déplacement :
str2 = std::move(str3); - 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;
}
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
1Fondamentaux de la POO
Fichiers externesBuild et Compilation C++Fichiers d'en-tête et Fichiers sourceEspaces de noms et PortéeIntroduction à la POO en C++Classes vs ObjetsLe pointeur 'this'Méthodes (Fonctions membres)Attributs (Membres de données)Bases des Ctors et DtorsRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d'instance vs membres statiquesGetters et SettersFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d'accès à l'héritageOrdre d'appel des Ctor et DtorRedéfinition de méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif - Hiérarchie des employés10Présentation de la STL
Présentation et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - Fréquence des mots13Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonFabrique & Fabrique abstraitePatron MonteurPatron ObservateurPatron Stratégie2Gestion de la mémoire
Mémoire Pile vs TasPointeurs et RéférencesMémoire dynamique (new/delete)Smart Pointers en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d'accès en C++Spécificateurs d'accès en profondeurMasquage d'informationStruct vs ClassClasses imbriquées et internesRécapitulatif - Système de gestion d'étudiants8Polymorphisme
Polymorphisme : Compilation vs ExécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d'interfaces en C++Dynamic Casting & RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition vs HéritageMixins via CRTPIdiome PimplEffacement de typeEnum Classes & Typage fortGestion des exceptions en POOHiérarchies d'exceptions personnalisées14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeRAII en tant que patron3Constructeurs et Destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d'initialisation du constructeurConstructeurs déléguésAnalyse approfondie du destructeurRègle de trois / cinq / zéroRécapitulatif - Classe String6Surcharge d'opérateurs
Introduction à la surcharge d'opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l'opérateur d'affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif - Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation de templatesTemplates variadiquesBases de SFINAE & Type TraitsRécapitulatif - Conteneur générique