Costruttore di spostamento
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 21 di 104.
Un costruttore di spostamento trasferisce le risorse da un oggetto temporaneo invece di copiarle. Mentre un costruttore di copia duplica i dati, un costruttore di spostamento "ruba" le risorse, lasciando l'oggetto sorgente in uno stato valido ma vuoto. In questo modo si evitano costose copie profonde quando l'oggetto sorgente sta per essere distrutto comunque.
Il costruttore di spostamento accetta un riferimento rvalue, indicato con &&:
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// Costruttore di spostamento
Buffer(Buffer&& other) noexcept {
data = other.data; // Acquisisci il puntatore
size = other.size;
other.data = nullptr; // Lascia l'oggetto sorgente in uno stato valido
other.size = 0;
}
~Buffer() { delete[] data; }
};La differenza fondamentale rispetto alla copia: non allochiamo nuova memoria. Ci limitiamo a prendere possesso della memoria esistente e a impostare il puntatore dell'origine su nullptr, così il suo distruttore non eliminerà i nostri dati.
I costruttori di spostamento vengono chiamati quando si inizializza a partire da oggetti temporanei o quando si usa std::move():
Buffer createBuffer() {
return Buffer(1000); // Costruttore di spostamento utilizzato (valore restituito)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // Spostamento esplicito - b1 ora è vuotoContrassegna i costruttori di spostamento come noexcept quando possibile. Questo indica al compilatore che l'operazione non genererà eccezioni, consentendo importanti ottimizzazioni nei contenitori come std::vector.
Sfida
FacileCostruiamo un sistema di pacchetti di dati che dimostri come i costruttori di spostamento trasferiscano in modo efficiente la proprietà delle risorse. Creerai una classe DataPacket che gestisce un array di byte allocato dinamicamente e implementerai un costruttore di spostamento che «ruba» i dati invece di copiarli.
Creerai due file per organizzare il codice:
DataPacket.h: definisci una classeDataPacketche gestisce un payload di dati interi. La classe dovrebbe avere:- Membri privati: un puntatore a un array di interi (
payload), unsizeper il numero di elementi e unpacketId(stringa) per identificare il pacchetto - Un costruttore con parametri che accetta un ID del pacchetto e una dimensione, alloca l'array e lo riempie con valori da 0 a dimensione-1. Stampa
"Packet <id> created with size <size>" - Un costruttore di spostamento contrassegnato con
noexceptche trasferisce la proprietà del payload dall'oggetto sorgente. Stampa"Packet <id> moved". Ricorda di lasciare l'oggetto sorgente in uno stato vuoto ma valido (nullptr, dimensione 0) - Un distruttore che libera la memoria se il puntatore non è null e stampa
"Packet <id> destroyed" - Un metodo
getSize()che restituisce la dimensione corrente - Un metodo
getId()che restituisce l'ID del pacchetto - Un metodo
getSum()che restituisce la somma di tutti gli elementi del payload (restituisce 0 se il payload è null)
- Membri privati: un puntatore a un array di interi (
main.cpp: dimostra la costruzione per spostamento leggendo da input un ID del pacchetto e una dimensione. Poi:- Crea un
DataPacketchiamatooriginalcon i valori di input - Stampa
"Original - ID: <id>, Size: <size>, Sum: <sum>" - Crea un nuovo pacchetto chiamato
transferredspostando i dati daoriginalconstd::move() - Stampa
"After move:" - Stampa
"Original - ID: <id>, Size: <size>, Sum: <sum>" - Stampa
"Transferred - ID: <id>, Size: <size>, Sum: <sum>"
- Crea un
Il formato di input sarà:
- Prima riga: ID del pacchetto (stringa)
- Seconda riga: dimensione (intero)
Dopo lo spostamento, il pacchetto originale dovrebbe avere dimensione 0 e somma 0 (poiché i suoi dati sono stati trasferiti), mentre il pacchetto trasferito dovrebbe contenere tutti i dati originali. Questo dimostra come la semantica di spostamento eviti costose copie profonde semplicemente trasferendo la proprietà del puntatore.
Includi il file header in main.cpp usando #include "DataPacket.h" e non dimenticare di includere <utility> per std::move().
Provalo tu
#include <iostream>
#include <string>
#include <utility>
#include "DataPacket.h"
using namespace std;
int main() {
// Leggi l'input
string packetId;
int size;
cin >> packetId;
cin >> size;
// TODO: Crea un DataPacket chiamato 'original' con i valori di input
// TODO: Stampa "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: Crea un nuovo pacchetto chiamato 'transferred' spostando i dati da original
// Suggerimento: usa std::move()
// TODO: Stampa "After move:"
// TODO: Stampa "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: Stampa "Transferred - ID: <id>, Size: <size>, Sum: <sum>"
return 0;
}
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
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online