Menu
Coddy logo textTech

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.

challenge icon

Défi

Facile

Construisons 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 classe DataBuffer, 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::string et une taille size_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 (ou empty destroyed si 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 noexcept et de laisser l'objet source dans un état vide valide (nullptr, taille 0, nom "empty").

  • 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 :
    1. Un nom pour ton tampon (chaîne de caractères)
    2. Une taille pour ton tampon (entier)

    Crée un DataBuffer avec le nom et la taille fournis. Crée ensuite un second tampon en effectuant un déplacement depuis le premier à l'aide de std::move(). Après le déplacement, affiche l'état des deux tampons :

    • Original: [name] size=[size]
    • New: [name] size=[size]

    Inclure <utility> pour std::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 destroyed

Avec 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 destroyed

Remarque 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;
}
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

Entraînez-vous par vous-même : Compilateur C++ en ligne