La semántica de movimiento no es necesariamente una mejora tan grande cuando devuelve un valor, y cuando / si usa un shared_ptr(o algo similar) probablemente sea pesimista prematuramente. En realidad, casi todos los compiladores razonablemente modernos hacen lo que se llama Return Value Optimization (RVO) y Named Return Value Optimization (NRVO). Esto significa que cuando devuelve un valor, en lugar de copiar el valor en absoluto, simplemente pasan un puntero / referencia oculto a donde se asignará el valor después del retorno, y la función lo usa para crear el valor donde va a terminar. El estándar C ++ incluye disposiciones especiales para permitir esto, por lo que incluso (por ejemplo) su constructor de copia tiene efectos secundarios visibles, no es necesario usar el constructor de copia para devolver el valor. Por ejemplo:
#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;
};
La idea básica aquí es bastante simple: crear una clase con suficiente contenido que preferiríamos evitar copiar, si es posible (la std::vectorllenamos con 32767 entradas aleatorias). Tenemos un copiador explícito que nos mostrará cuándo / si se copia. También tenemos un poco más de código para hacer algo con los valores aleatorios en el objeto, por lo que el optimizador no eliminará (al menos fácilmente) todo sobre la clase solo porque no hace nada.
Luego tenemos un código para devolver uno de estos objetos de una función, y luego usamos la suma para asegurarnos de que el objeto realmente se crea, no solo se ignora por completo. Cuando lo ejecutamos, al menos con los compiladores más recientes / modernos, encontramos que el constructor de copias que escribimos nunca se ejecuta en absoluto, y sí, estoy bastante seguro de que incluso una copia rápida con un shared_ptraún es más lenta que no copiar en absoluto.
Mudarse le permite hacer una buena cantidad de cosas que simplemente no podría hacer (directamente) sin ellas. Considere la parte de "fusión" de un tipo de fusión externa: tiene, por ejemplo, 8 archivos que va a fusionar. Idealmente, le gustaría poner los 8 de esos archivos en un vector- pero dado que vector(a partir de C ++ 03) necesita poder copiar elementos, y ifstreams no se puede copiar, está atascado con algunos unique_ptr/ shared_ptr, o algo en ese orden para poder ponerlos en un vector. Tenga en cuenta que incluso si (por ejemplo) que reserveel espacio en el vectorpor lo que estamos seguros de que nuestros ifstreamnunca realmente se copiará s, el compilador no sabrá que, por lo que el código no se compilará a pesar de que sabemos que el constructor de copia nunca será utilizado de todos modos.
Aunque todavía no se puede copiar, en C ++ 11 ifstream se puede mover. En este caso, los objetos probablemente serán no siempre pueden mover, pero el hecho de que podrían ser si es necesario mantiene el compilador feliz, para que podamos poner nuestros ifstreamobjetos de una vectorforma directa, sin ningún tipo de hacks puntero inteligente.
Sin embargo, un vector que se expande es un ejemplo bastante decente de un tiempo en que la semántica de movimiento realmente puede ser útil. En este caso, RVO / NRVO no ayudará, porque no estamos tratando con el valor de retorno de una función (o algo muy similar). Tenemos un vector que contiene algunos objetos, y queremos mover esos objetos a una nueva porción de memoria más grande.
En C ++ 03, eso se hizo creando copias de los objetos en la nueva memoria, y luego destruyendo los objetos antiguos en la memoria anterior. Sin embargo, hacer todas esas copias solo para tirar las viejas era una pérdida de tiempo. En C ++ 11, puede esperar que se muevan en su lugar. Esto generalmente nos permite, en esencia, hacer una copia superficial en lugar de una copia profunda (generalmente mucho más lenta). En otras palabras, con una cadena o un vector (solo para un par de ejemplos) simplemente copiamos los punteros en los objetos, en lugar de hacer copias de todos los datos a los que se refieren esos punteros.
shared_ptrcopia simple) y si la semántica de movimiento puede lograr lo mismo casi sin penalización de codificación, semántica y limpieza.