Sémantique de déplacement et rvalues
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy. Leçon 83 sur 104.
En C++, toute expression est soit une lvalue (possède une identité persistante, peut être adressée), soit une rvalue (temporaire, sur le point d’être détruite). Comprendre cette distinction permet de maîtriser la sémantique de déplacement — une optimisation puissante qui évite les copies inutiles.
Une référence rvalue, déclarée avec &&, se lie spécifiquement aux objets temporaires. Cela vous permet de « voler » des ressources à des objets qui sont de toute façon sur le point de disparaître :
#include <iostream>
#include <utility>
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : data(new int[s]), size(s) {
std::cout << "Constructed\n";
}
// Constructeur de déplacement - vole les ressources
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // Laisse la source dans un état valide
other.size = 0;
std::cout << "Moved\n";
}
~Buffer() { delete[] data; }
};
int main() {
Buffer b1(1000);
Buffer b2(std::move(b1)); // Déclenche le constructeur de déplacement
}La fonction std::move ne déplace en réalité rien : elle convertit simplement une lvalue en référence rvalue, signalant que vous êtes prêt à renoncer aux ressources de l’objet. Le déplacement proprement dit s’effectue dans le constructeur de déplacement ou l’opérateur d’affectation par déplacement.
La sémantique de déplacement améliore considérablement les performances lorsque vous travaillez avec des objets gourmands en ressources, comme des conteneurs ou des chaînes de caractères. Au lieu de copier en profondeur des mégaoctets de données, vous transférez simplement la propriété du pointeur — une opération en temps constant, quelle que soit la taille.
Défi
FacileConstruisons une classe DataBuffer qui gère des ressources et illustre la sémantique de déplacement en action. Tu verras comment le déplacement des ressources, au lieu de leur copie, peut améliorer considérablement l'efficacité lors du transfert de la propriété d'une mémoire allouée dynamiquement.
Tu organiseras ton code sur trois fichiers :
DataBuffer.h: définir ta classeDataBuffer, qui gère un tableau d'entiers alloué dynamiquement.Ta classe doit comporter des membres privés pour le pointeur vers les données (
int*), la taille (size_t) et un nom (std::string) afin de permettre de distinguer les tampons pendant les opérations.Déclare les éléments suivants :
- Un constructeur qui prend un nom
std::stringet une taillesize_t, alloue le tableau et affiche :[name] constructed with size [size] - Un constructeur de déplacement qui prend une référence rvalue, s'approprie les ressources et affiche :
[name] moved from [source_name](le tampon de destination prend le nom de la source) - Un destructeur qui affiche
[name] destroyed(ouempty destroyedsi le tampon a été déplacé) - Une méthode
getSize()qui renvoie la taille actuelle - Une méthode
getName()qui renvoie le nom du tampon
N'oublie pas de marquer ton constructeur de déplacement avec
noexceptet de laisser l'objet source dans un état vide valide (nullptr, taille 0, nom "empty").- Un constructeur qui prend un nom
DataBuffer.cpp: implémenter toutes les méthodes déclarées dans ton fichier d'en-tête. Lorsque le destructeur s'exécute, ne supprimer les données que si le pointeur n'est pas nul. Inclure<iostream>pour l'affichage.main.cpp: lire deux entrées :- Un nom pour ton tampon (chaîne de caractères)
- Une taille pour ton tampon (entier)
Crée un
DataBufferavec le nom et la taille fournis. Crée ensuite un second tampon en effectuant un déplacement depuis le premier à l'aide destd::move(). Après le déplacement, affiche l'état des deux tampons :Original: [name] size=[size]New: [name] size=[size]
Inclure
<utility>pourstd::move.
Par exemple, avec les entrées Alpha et 100 :
Alpha constructed with size 100
Alpha moved from Alpha
Original: empty size=0
New: Alpha size=100
Alpha destroyed
empty destroyedAvec les entrées Buffer et 50 :
Buffer constructed with size 50
Buffer moved from Buffer
Original: empty size=0
New: Buffer size=50
Buffer destroyed
empty destroyedRemarque comment le constructeur de déplacement transfère la propriété de la mémoire allouée sans copier aucune donnée. Le tampon d'origine est laissé dans un état vide mais valide et, lorsque les deux tampons sont détruits à la fin du programme, seul celui qui possède encore la mémoire la supprime réellement.
Essayez vous-même
#include <iostream>
#include <string>
#include <utility>
#include "DataBuffer.h"
int main() {
std::string name;
int size;
std::cin >> name;
std::cin >> size;
// TODO: Créer un DataBuffer avec le nom et la taille donnés
// TODO: Créer un second buffer en déplaçant le premier à l'aide de std::move()
// TODO: Afficher l'état des deux buffers :
// Original: [name] size=[size]
// New: [name] size=[size]
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 externesGénération et compilation en C++Fichiers d’en-tête et fichiers sourceEspaces de noms et portéeIntroduction à la POO en C++Classes et objetsLe pointeur « this »Méthodes (fonctions membres)Attributs (membres de données)Bases des constructeurs et destructeursRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d’instance et statiquesAccesseurs et mutateursFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d’accès de l’héritageOrdre d’appel des constructeurs et destructeursRedéfinition des méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif : hiérarchie des employés10Vue d’ensemble de la STL
Vue d’ensemble et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - fréquence des mots13Modèles de conception, partie 1
Introduction aux modèles de conceptionModèle SingletonFabrique et fabrique abstraiteModèle BuilderModèle ObserverModèle Strategy2Gestion de la mémoire
Mémoire de pile ou de tasPointeurs et référencesMémoire dynamique (new/delete)Pointeurs intelligents 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 de l’informationStruct vs classeClasses imbriquées et internesRécapitulatif - Système de gestion des dossiers étudiants8Polymorphisme
Polymorphisme à la compilation vs à l’exécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d’interfaces en C++Conversion dynamique et RTTIRécapitulatif - Calculateur de formes3Constructeurs et destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d’initialisation des constructeursConstructeurs déléguésApprofondissement des destructeursRègle des trois / cinq / zéroRécapitulatif - Classe String6Surcharge des opérateurs
Introduction à la surcharge des 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 des templatesTemplates variadiquesBases de SFINAE et des traits de typesRécapitulatif - Conteneur générique12Fonctionnalités modernes de C++
Sémantique de déplacement et rvaluesTransfert parfaitExpressions lambda en profondeurstd::function et std::bindconstexpr et constevalLiaisons structuréesoptional, variant, anyEntraînez-vous par vous-même : Compilateur C++ en ligne