Menu
Coddy logo textTech

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 mouvement - 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 une référence rvalue, signalant que vous êtes prêt à céder les ressources de l'objet. Le déplacement réel se produit 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 gourmands en ressources comme les conteneurs ou les 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.

challenge icon

Défi

Facile

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

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

    Créez un DataBuffer avec le nom et la taille donnés. Créez ensuite un second tampon en déplaçant le premier à l'aide de std::move(). Après le déplacement, affichez l'état des deux tampons :

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

    Incluez <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

Remarquez comment le constructeur de mouvement 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