Menu
Coddy logo textTech

Semantica di spostamento e rvalue

Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 83 di 104.

In C++, ogni espressione è un lvalue (ha un’identità persistente, può essere indirizzata) oppure un rvalue (temporanea, in procinto di essere distrutta). Comprendere questa distinzione permette di sfruttare la semantica di spostamento: una potente ottimizzazione che evita copie non necessarie.

Un riferimento rvalue, dichiarato con &&, si lega specificamente agli oggetti temporanei. Questo ti permette di "sottrarre" risorse agli oggetti che stanno per scomparire comunque:

#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";
    }
    
    // Constructor de movimiento - se apropia de los recursos
    Buffer(Buffer&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;  // Deja el objeto de origen en un estado válido
        other.size = 0;
        std::cout << "Moved\n";
    }
    
    ~Buffer() { delete[] data; }
};

int main() {
    Buffer b1(1000);
    Buffer b2(std::move(b1));  // Activa el constructor de movimiento
}

La funzione std::move in realtà non sposta nulla: converte semplicemente un lvalue in un riferimento rvalue, segnalando che sei disposto a cedere le risorse dell’oggetto. Lo spostamento effettivo avviene nel costruttore di spostamento o nell’operatore di assegnazione per spostamento.

La semantica di spostamento migliora notevolmente le prestazioni quando si lavora con oggetti che richiedono molte risorse, come i contenitori o le stringhe. Invece di copiare in profondità megabyte di dati, trasferisci semplicemente la proprietà dei puntatori: un’operazione a tempo costante, indipendentemente dalle dimensioni.

challenge icon

Sfida

Facile

Costruiamo una classe DataBuffer che gestisce le risorse e dimostra la semantica di spostamento in azione. Vedrai come spostare le risorse invece di copiarle può migliorare notevolmente l'efficienza quando si trasferisce la proprietà della memoria allocata dinamicamente.

Organizzerai il codice in tre file:

  • DataBuffer.h: definisci la classe DataBuffer, che gestisce un array di interi allocato dinamicamente.

    La tua classe dovrebbe avere membri privati per il puntatore ai dati (int*), la dimensione (size_t) e un nome (std::string) per aiutarti a distinguere i buffer durante le operazioni.

    Dichiara quanto segue:

    • Un costruttore che accetta un nome std::string e una dimensione size_t, alloca l'array e stampa: [name] constructed with size [size]
    • Un costruttore di spostamento che accetta un riferimento rvalue, acquisisce le risorse e stampa: [name] moved from [source_name] (dove il buffer di destinazione acquisisce il nome della sorgente)
    • Un distruttore che stampa [name] destroyed (oppure empty destroyed se il buffer è stato spostato)
    • Un metodo getSize() che restituisce la dimensione corrente
    • Un metodo getName() che restituisce il nome del buffer

    Ricorda di contrassegnare il costruttore di spostamento come noexcept e di lasciare l'oggetto sorgente in uno stato vuoto ma valido (nullptr, dimensione 0, nome "empty").

  • DataBuffer.cpp: implementa tutti i metodi dichiarati nell'header. Quando viene eseguito il distruttore, elimina i dati solo se il puntatore non è null. Includi <iostream> per l'output.
  • main.cpp: leggi due valori in input:
    1. Un nome per il buffer (stringa)
    2. Una dimensione per il buffer (intero)

    Crea un DataBuffer con il nome e la dimensione specificati. Poi crea un secondo buffer spostando il primo con std::move(). Dopo lo spostamento, stampa lo stato di entrambi i buffer:

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

    Includi <utility> per std::move.

Per esempio, con gli input Alpha e 100:

Alpha constructed with size 100
Alpha moved from Alpha
Original: empty size=0
New: Alpha size=100
Alpha destroyed
empty destroyed

Con gli input Buffer e 50:

Buffer constructed with size 50
Buffer moved from Buffer
Original: empty size=0
New: Buffer size=50
Buffer destroyed
empty destroyed

Nota come il costruttore di spostamento trasferisca la proprietà della memoria allocata senza copiare alcun dato. Il buffer originale rimane in uno stato vuoto ma valido e, quando entrambi i buffer vengono distrutti alla fine del programma, solo quello che possiede ancora la memoria la elimina effettivamente.

Provalo tu

#include <iostream>
#include <string>
#include <utility>
#include "DataBuffer.h"

int main() {
    std::string name;
    int size;
    
    std::cin >> name;
    std::cin >> size;
    
    // TODO: Crea un DataBuffer con el nombre y el tamaño dados
    
    // TODO: Crea un segundo búfer moviendo el primero con std::move()
    
    // TODO: Imprime el estado de ambos búferes:
    // Original: [name] size=[size]
    // Nuevo: [name] size=[size]
    
    return 0;
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore C++ online