valor tipos referencia por pase pasaje parametros net enviar entrada c++ c++11 move

c++ - tipos - ¿Debo devolver un parámetro de referencia rvalue por referencia rvalue?



pase por referencia (4)

Esto me lleva a creer que se produciría una copia desde el valor std :: string & return de la versión de referencia de lvalue de la transformación (...) en el valor de retorno std :: string.

¿Es eso correcto?

La versión de referencia de retorno no permitirá que se realice la copia de std :: string, pero la versión de valor de retorno tendrá copia, si el compilador no hace RVO. Sin embargo, RVO tiene su limitación, por lo que C ++ 11 agrega la referencia de valor r y mueve constructor / asignación / std :: se mueve para ayudar a manejar esta situación. Sí, RVO es más eficiente que el movimiento semántico, el movimiento es más barato que la copia pero más caro que el RVO.

¿Es mejor mantener mi versión std :: string && transform (...)?

Esto es de alguna manera interesante y extraño. Como respondió Potatoswatter,

std::string transform(std::string&& input) { return transform(input); // calls the lvalue reference version }

Deberías llamar a std :: move manualmente.

Sin embargo, puede hacer clic en este enlace de developerworks: RVO VS std :: move para ver más detalles, lo que explica su problema claramente.

Tengo una función que modifica las referencias std::string& lvalue en el lugar, devolviendo una referencia al parámetro de entrada:

std::string& transform(std::string& input) { // transform the input string ... return input; }

Tengo una función auxiliar que permite realizar las mismas transformaciones en línea en las referencias de valor:

std::string&& transform(std::string&& input) { return std::move(transform(input)); // calls the lvalue reference version }

Observe que devuelve una referencia rvalue .

He leído varias preguntas sobre SO relacionadas con la devolución de referencias de valores ( here y here por ejemplo), y he llegado a la conclusión de que esta es una mala práctica.

Por lo que he leído, parece que el consenso es que, dado que los valores de retorno son valores r, además de tener en cuenta el RVO, la devolución por valor sería igual de eficiente:

std::string transform(std::string&& input) { return transform(input); // calls the lvalue reference version }

Sin embargo, también he leído que devolver los parámetros de la función impide la optimización RVO (por ejemplo, here y here )

Esto me lleva a creer que se produciría una copia desde el valor std::string& return de la versión de referencia de lvalue de la transform(...) en el valor de retorno std::string .

¿Es eso correcto?

¿Es mejor mantener mi versión std::string&& transform(...) ?


Algunos tiempos de ejecución (no representativos) para las versiones anteriores de la transform :

correr en coliru

#include <iostream> #include <time.h> #include <sys/time.h> #include <unistd.h> using namespace std; double GetTicks() { struct timeval tv; if(!gettimeofday (&tv, NULL)) return (tv.tv_sec*1000 + tv.tv_usec/1000); else return -1; } std::string& transform(std::string& input) { // transform the input string // e.g toggle first character if(!input.empty()) { if(input[0]==''A'') input[0] = ''B''; else input[0] = ''A''; } return input; } std::string&& transformA(std::string&& input) { return std::move(transform(input)); } std::string transformB(std::string&& input) { return transform(input); // calls the lvalue reference version } std::string transformC(std::string&& input) { return std::move( transform( input ) ); // calls the lvalue reference version } string getSomeString() { return string("ABC"); } int main() { const int MAX_LOOPS = 5000000; { double start = GetTicks(); for(int i=0; i<MAX_LOOPS; ++i) string s = transformA(getSomeString()); double end = GetTicks(); cout << "/nRuntime transformA: " << end - start << " ms" << endl; } { double start = GetTicks(); for(int i=0; i<MAX_LOOPS; ++i) string s = transformB(getSomeString()); double end = GetTicks(); cout << "/nRuntime transformB: " << end - start << " ms" << endl; } { double start = GetTicks(); for(int i=0; i<MAX_LOOPS; ++i) string s = transformC(getSomeString()); double end = GetTicks(); cout << "/nRuntime transformC: " << end - start << " ms" << endl; } return 0; }

salida

g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out Runtime transformA: 444 ms Runtime transformB: 796 ms Runtime transformC: 434 ms


No hay una respuesta correcta, pero devolver por valor es más seguro.

He leído varias preguntas sobre SO relacionadas con la devolución de referencias de valores, y he llegado a la conclusión de que esta es una mala práctica.

Devolver una referencia a un parámetro impone un contrato a la persona que llama que

  1. El parámetro no puede ser temporal (que es justo lo que representan las referencias de valor), o
  2. El valor de retorno no se conservará más allá del siguiente punto y coma en el contexto de la persona que llama (cuando los temporales se destruyen).

Si la persona que llama pasa un mensaje temporal e intenta guardar el resultado, obtiene una referencia pendiente.

Por lo que he leído, parece que el consenso es que, dado que los valores de retorno son valores r, además de tener en cuenta el RVO, la devolución por valor sería igual de eficiente:

Devolver por valor agrega una operación de movimiento-construcción. El costo de esto es generalmente proporcional al tamaño del objeto. Mientras que la devolución por referencia solo requiere que la máquina se asegure de que una dirección esté en un registro, la devolución por valor requiere poner a cero un par de punteros en el parámetro std::string y colocar sus valores en una nueva std::string a devolver.

Es barato, pero distinto de cero.

La dirección que toma actualmente la biblioteca estándar es, sorprendentemente, ser rápida e insegura y devolver la referencia. (La única función que sé que realmente hace esto es std::get from <tuple> ). A medida que sucede, presenté una propuesta al comité de lenguaje central de C ++ para la resolución de este problema, se está realizando una revisión. , y hoy mismo he comenzado a investigar la implementación. Pero es complicado, y no es una cosa segura.

std::string transform(std::string&& input) { return transform(input); // calls the lvalue reference version }

El compilador no generará un move aquí. Si la input no era una referencia en absoluto, y usted return input; lo haría, pero no tiene ninguna razón para creer que la transform devolverá la input solo porque era un parámetro, y de todos modos no deducirá la propiedad del tipo de referencia de valor. (Ver C ++ 14 §12.8 / 31-32.)

Necesitas hacer:

return std::move( transform( input ) );

o equivalente

transform( input ); return std::move( input );


Si su pregunta está orientada exclusivamente a la optimización, es mejor no preocuparse por cómo aprobar o devolver un argumento. el compilador es lo suficientemente inteligente como para estirar su código en cualquiera de los pases de referencia pura, copia de la elision, funciones en línea e incluso mover la semántica si es el método más rápido.

Básicamente, mover la semántica puede beneficiarlo en algunos casos esotéricos. digamos que tengo una matriz de objetos que contiene el double** como variable miembro y este puntero apunta a una matriz bidimensional de double . ahora digamos que tengo esta expresión:
Matrix a = b+c;
un constructor de copia (o un operador de asignación, en este caso) obtendrá la suma de b y c como valor añadido, la pasará como referencia constante, reasignará m*n cantidad de doubles en a puntero interno, luego, se ejecutará en a+b suma-array y copiará sus valores uno por uno. El cálculo fácil muestra que puede tomar hasta O(nm) pasos (que pueden generarse a O(n^2) ). mover la semántica solo volverá a cablear ese double** oculto double** del temprario en a puntero interno. lleva O(1) .
ahora pensemos en std::string por un momento: pasarlo como una referencia toma O(1) pasos (tomar la dirección de la memoria, pasarla, eliminarla, etc., esto no es lineal en ningún caso). pasarlo como r-valor-referencia requiere que el programa lo pase como referencia, volver a cablear ese C- char* subyacente oculto que contiene el búfer interno, anular el original (o intercambiar entre ellos), size y capacity copia y Muchas más acciones. podemos ver que aunque todavía estamos en la zona O(1) , en realidad pueden haber MÁS pasos que simplemente pasarla como una referencia regular.

Bueno, la verdad es que no lo comparé, y la discusión aquí es puramente teórica. Sin embargo, mi primer párrafo sigue siendo cierto. Asumimos muchas cosas como desarrolladores, pero a menos que lo evaluemos todo hasta la muerte, el compilador simplemente sabe mejor que nosotros en el 99% del tiempo.

Tomando en cuenta este argumento, diría que lo mantenga como un paso de referencia y no mueva la semántica, ya que es compatible con las contraseñas y mucho más comprendido para los desarrolladores que aún no dominan C ++ 11.