Spostare la semantica non è necessariamente un grande miglioramento quando si restituisce un valore - e quando / se si usa un shared_ptr
(o qualcosa di simile) probabilmente si sta pessimizzando prematuramente. In realtà, quasi tutti i compilatori ragionevolmente moderni fanno ciò che si chiama Return Value Optimization (RVO) e Named Return Value Optimization (NRVO). Ciò significa che quando si restituisce un valore, invece di copiare effettivamente il valore a tutti , si passa semplicemente un puntatore / riferimento nascosto a cui verrà assegnato il valore dopo il ritorno e la funzione lo usa per creare il valore in cui finirà. Lo standard C ++ include disposizioni speciali per consentire ciò, quindi anche se (ad esempio) il costruttore di copia ha effetti collaterali visibili, non è necessario utilizzare il costruttore di copie per restituire il valore. Ad esempio:
#include <vector>
#include <numeric>
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <iterator>
class X {
std::vector<int> a;
public:
X() {
std::generate_n(std::back_inserter(a), 32767, ::rand);
}
X(X const &x) {
a = x.a;
std::cout << "Copy ctor invoked\n";
}
int sum() { return std::accumulate(a.begin(), a.end(), 0); }
};
X func() {
return X();
}
int main() {
X x = func();
std::cout << "sum = " << x.sum();
return 0;
};
L'idea di base qui è abbastanza semplice: creare una classe con un contenuto sufficiente preferiremmo evitare di copiarla, se possibile (il std::vector
viene riempito con 32767 valori casuali). Abbiamo un copyctor esplicito che ci mostrerà quando / se verrà copiato. Abbiamo anche un po 'più di codice per fare qualcosa con i valori casuali nell'oggetto, quindi l'ottimizzatore non eliminerà (almeno facilmente) tutto ciò che riguarda la classe solo perché non fa nulla.
Abbiamo quindi del codice per restituire uno di questi oggetti da una funzione e quindi utilizzare la somma per garantire che l'oggetto sia realmente creato, non solo ignorato completamente. Quando lo eseguiamo, almeno con i compilatori più recenti / moderni, scopriamo che il programma di copia che abbiamo scritto non viene mai eseguito affatto, e sì, sono abbastanza sicuro che anche una copia veloce con shared_ptr
sia ancora più lenta di non fare nessuna copia.
Il movimento ti consente di fare un buon numero di cose che semplicemente non potresti fare (direttamente) senza di loro. Considera la parte "unione" di un ordinamento di unione esterno: hai 8 file che unirai insieme. Idealmente ti piacerebbe inserire tutti gli 8 di questi file in vector
- ma poiché vector
(come in C ++ 03) deve essere in grado di copiare gli elementi, e ifstream
s non può essere copiato , sei bloccato con un po 'di unique_ptr
/ shared_ptr
, o qualcosa su quell'ordine per essere in grado di metterli in un vettore. Nota che anche se (per esempio) abbiamo reserve
spazio in vector
, siamo sicuri che il nostro ifstream
s non verrà mai copiato, il compilatore non lo saprà, quindi il codice non verrà compilato nemmeno anche se noi sappiamo che il costruttore di copie non sarà mai usato comunque.
Anche se non può ancora essere copiato, in C ++ 11 un ifstream
può essere spostato. In questo caso, gli oggetti probabilmente non saranno mai spostati, ma il fatto che potrebbero essere se necessario mantiene il compilatore felice, quindi possiamo mettere i nostri ifstream
oggetti in vector
direttamente , senza alcun hacker puntatore intelligente.
Un vettore che espande è un esempio abbastanza decente di un tempo in cui la semantica può essere davvero utile / utile. In questo caso, RVO / NRVO non aiuterà, perché non abbiamo a che fare con il valore restituito da una funzione (o qualcosa di molto simile). Abbiamo un vettore che contiene alcuni oggetti e vogliamo spostare quegli oggetti in un nuovo blocco più grande di memoria.
In C ++ 03, ciò è stato fatto creando copie degli oggetti nella nuova memoria, quindi distruggendo i vecchi oggetti nella vecchia memoria. Fare tutte quelle copie solo per buttare via quelle vecchie, tuttavia, è stata una perdita di tempo. In C ++ 11, puoi aspettarti che vengano spostati. Questo di solito ci consente, in sostanza, di fare una copia superficiale invece di una copia profonda (generalmente molto più lenta). In altre parole, con una stringa o un vettore (solo per un paio di esempi) basta copiare il puntatore (o gli oggetti) negli oggetti, invece di fare copie di tutti i dati a cui si riferiscono i puntatori.