Sémantique de mouvement et Rvalues
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 83 sur 104.
En C++, chaque 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 débloque 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 rien en réalité — elle effectue simplement un cast d'une lvalue vers une référence rvalue, signalant que vous êtes prêt à céder les ressources de l'objet. Le déplacement réel s'effectue dans le constructeur de déplacement ou l'opérateur d'affectation par déplacement.
La sémantique de mouvement améliore considérablement les performances lors de l'utilisation d'objets lourds en ressources tels que des conteneurs ou des chaînes de caractères. Au lieu d'effectuer une copie profonde de 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 de gestion de ressources qui démontre la sémantique de mouvement en action. Vous verrez comment le déplacement de ressources au lieu de leur copie peut considérablement améliorer l'efficacité lors du transfert de propriété d'une mémoire allouée dynamiquement.
Vous organiserez votre code sur trois fichiers :
DataBuffer.h: Définissez votre classeDataBufferqui gère un tableau d'entiers alloué dynamiquement.Votre classe doit avoir des membres privés pour le pointeur de données (
int*), la taille (size_t) et un nom (std::string) pour aider à suivre quel tampon est lequel pendant les opérations.Déclarez ce qui suit :
- Un constructeur qui prend un nom
std::stringet une taillesize_t, alloue le tableau et affiche :[name] constructed with size [size] - Un constructeur de mouvement qui prend une référence rvalue, vole les ressources et affiche :
[name] moved from [source_name](où 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 retourne la taille actuelle - Une méthode
getName()qui retourne le nom du tampon
N'oubliez pas de marquer votre constructeur de mouvement comme
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émentez toutes les méthodes déclarées dans votre en-tête. Lorsque le destructeur s'exécute, ne supprimez les données que si le pointeur n'est pas nul. Incluez<iostream>pour la sortie.main.cpp: Lisez deux entrées :- Un nom pour votre tampon (chaîne de caractères)
- Une taille pour votre tampon (entier)
Créez un
DataBufferavec le nom et la taille donnés. Créez ensuite un second tampon en déplaçant le premier à l'aide destd::move(). Après le mouvement, affichez l'état des deux tampons :Original: [name] size=[size]New: [name] size=[size]
Incluez
<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 destroyedRemarquez comment le constructeur de mouvement transfère la propriété de la mémoire allouée sans copier de données. 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.
Aide-mémoire
Les expressions C++ sont soit des lvalues (persistantes, adressables), soit des rvalues (temporaires). La sémantique de mouvement (move semantics) optimise les performances en transférant les ressources des objets temporaires au lieu de les copier.
Références de Rvalue
Déclarées avec &&, les références de rvalue se lient aux objets temporaires :
Buffer(Buffer&& other) // Paramètre de référence de rvalueConstructeur de mouvement
Vole les ressources d'un objet temporaire. Marquez-le comme noexcept et laissez la source dans un état vide valide :
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // Laisse la source valide
other.size = 0;
}std::move
Convertit une lvalue en une référence de rvalue, signalant la volonté de transférer des ressources. Incluez <utility> :
Buffer b1(1000);
Buffer b2(std::move(b1)); // Déclenche le constructeur de mouvementstd::move ne déplace rien en soi - le transfert réel se produit dans le constructeur de mouvement ou l'opérateur d'affectation par mouvement.
Avantages
La sémantique de mouvement permet un transfert de ressources en temps constant, quelle que soit la taille de l'objet, évitant ainsi les copies profondes coûteuses de structures de données volumineuses.
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 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érique12Fonctionnalités du C++ moderne
Sémantique de mouvement et RvaluesTransfert parfaitExpressions Lambda en profondeurstd::function et std::bindconstexpr et constevalLiaisons structuréesoptional, variant, any