Spostare la semantica non è necessariamente un grande miglioramento quando si restituisce un valore - e quando / se si utilizza un shared_ptr
(o qualcosa di simile) probabilmente si sta prematuramente pessimizzando. In realtà, quasi tutti i compilatori ragionevolmente moderni fanno ciò che viene chiamato Return Value Optimization (RVO) e Named Return Value Optimization (NRVO). Questo significa che quando si sta restituendo un valore, in realtà invece di copiare il valore a tutti, passano semplicemente un puntatore / riferimento nascosto a dove verrà assegnato il valore dopo il ritorno e la funzione lo utilizza per creare il valore dove sta per finire. Lo standard C ++ include disposizioni speciali per consentire ciò, quindi anche se (ad esempio) il costruttore di copie ha effetti collaterali visibili, non è necessario utilizzare il costruttore di copie per restituire il valore. Per 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 abbastanza contenuto che preferiremmo evitare di copiarlo, se possibile ( std::vector
riempiamo con 32767 ints casuali). Abbiamo un ctor esplicito di copia 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 un po 'di 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 costruttore di copie che abbiamo scritto non funziona mai - e sì, sono abbastanza sicuro che anche una copia veloce con un shared_ptr
sia ancora più lenta rispetto a non fare alcuna copia affatto.
Lo spostamento ti consente di fare un discreto numero di cose che semplicemente non potresti fare (direttamente) senza di esse. Considera la parte "unisci" di un ordinamento di unione esterno: hai, diciamo, 8 file che unirai insieme. Idealmente ti piacerebbe mettere tutti e 8 questi file in un vector
- ma poiché vector
(a partire da C ++ 03) deve essere in grado di copiare elementi, e ifstream
non è possibile copiarli, sei bloccato con alcuni unique_ptr
/ shared_ptr
, o qualcosa in quell'ordine per poterli mettere in un vettore. Si noti che anche se (per esempio) abbiamo reserve
spazio in vector
modo siamo sicuri che i nostri ifstream
s sarà mai veramente essere copiati, il compilatore non sa che, in modo che il codice non verrà compilato anche se noi conosciamo il costruttore di copia non sarà mai usato comunque.
Anche se non può ancora essere copiato, in C ++ 11 è ifstream
possibile spostarlo. In questo caso, gli oggetti probabilmente non verranno mai spostati, ma il fatto che possano esserlo, se necessario, rende felice il compilatore, in modo da poter mettere i nostri ifstream
oggetti in modo vector
diretto, senza alcun hack di puntatore intelligente.
Un vettore che si espande è un esempio abbastanza decente di un tempo in cui spostare la semantica può essere / sono comunque utili. In questo caso, RVO / NRVO non aiuterà, perché non stiamo trattando il valore di ritorno da una funzione (o qualcosa di molto simile). Abbiamo un vettore che contiene alcuni oggetti e vogliamo spostare quegli oggetti in un nuovo pezzo di memoria più grande.
In C ++ 03, ciò è stato creato 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, era piuttosto una perdita di tempo. In C ++ 11, invece, puoi aspettarti che vengano spostati. Questo in genere ci consente, in sostanza, di fare una copia superficiale anziché una copia profonda (generalmente molto più lenta). In altre parole, con una stringa o un vettore (solo per un paio di esempi) copiamo solo i puntatori negli oggetti, invece di fare copie di tutti i dati a cui fanno riferimento questi puntatori.
shared_ptr
bene solo per motivi di copia veloce) e se spostare la semantica può ottenere lo stesso con quasi nessuna penalità di codifica, semantica e pulizia.