semantica oposicion lexico lexica ejemplos definicion c++ c++11 move-semantics c++-standard-library

oposicion - ¿Es este el uso correcto de la semántica ''move'' de C++?



semantica lexical (4)

Esta noche he estado echando un vistazo al código en el que he estado trabajando durante los últimos días, y comencé a leer acerca de la semántica de movimientos, específicamente std :: move. ¡Tengo algunas preguntas para preguntarte a los profesionales para asegurarte de que voy por el buen camino y no hacer suposiciones estúpidas!

En primer lugar:

1) Originalmente, mi código tenía una función que devolvía un vector grande:

template<class T> class MyObject { public: std::vector<T> doSomething() const; { std::vector<T> theVector; // produce/work with a vector right here return(theVector); }; // eo doSomething }; // eo class MyObject

Dado que "theVector" es temporal en esto y "throw-away", modifiqué la función para:

std::vector<T>&& doSomething() const; { std::vector<T> theVector; // produce/work with a vector right here return(static_cast<std::vector<T>&&>(theVector)); }; // eo doSomething

¿Es esto correcto? ¿Alguna trampa al hacerlo de esta manera?

2) Noté en una función que tengo que devuelve std::string que automáticamente llamó al constructor de movimientos. Al depurar en Return of the String (gracias, Aragorn), noté que se llamaba constructor de movimientos explícito. ¿Por qué hay uno para la clase de cadena y no vector?

No tuve que hacer ninguna modificación a esta función para aprovechar la semántica de movimientos:

// below, no need for std::string&& return value? std::string AnyConverter::toString(const boost::any& _val) const { string ret; // convert here return(ret); // No need for static_cast<std::string&&> ? }; // eo toString

3) Finalmente, quería hacer algunas pruebas de rendimiento, ¿son los resultados increíblemente rápidos que obtuve debido a la semántica de std :: move o mi compilador (VS2010) también hizo algo de optimización?

(Implementación de _getMilliseconds() omitida por brevedad)

std::vector<int> v; for(int a(0); a < 1000000; ++a) v.push_back(a); std::vector<int> x; for(int a(0); a < 1000000; ++a) x.push_back(a); int s1 = _getMilliseconds(); std::vector<int> v2 = v; int s2 = _getMilliseconds(); std::vector<int> v3 = std::move(x); int s3 = _getMilliseconds(); int result1 = s2 - s1; int result2 = s3 - s2;

Los resultados fueron, obviamente, asombrosos. result1, una asignación estándar, tomó 630ms. El segundo resultado fue 0ms. ¿Es esta una buena prueba de rendimiento de estas cosas?

Sé que algo de esto es obvio para muchos de ustedes, pero quiero asegurarme de que entiendo la semántica justo antes de usar mi código.

¡Gracias por adelantado!


return(theVector);

Esto ya se mueve implícitamente debido a una regla de lenguaje especial, porque el theVector es un objeto local. Ver la sección 12.8 párrafos 34 y 35:

Cuando se cumplen ciertos criterios, una implementación puede omitir la construcción de copia / movimiento de un objeto de clase , incluso si el constructor de copia / movimiento y / o el destructor para el objeto tienen efectos secundarios. En tales casos, la implementación trata la fuente y el destino de la operación de copiar / mover omitida como simplemente dos formas diferentes de referirse al mismo objeto, y la destrucción de ese objeto ocurre en el momento posterior en que los dos objetos habrían sido destruido sin la optimización. Esta elisión de las operaciones de copiar / mover, llamada copia elisión, está permitida en las siguientes circunstancias (que pueden combinarse para eliminar copias múltiples):

- en una instrucción de retorno en una función con un tipo de retorno de clase, cuando la expresión es el nombre de un objeto automático no volátil con el mismo tipo cv no calificado que el tipo de retorno de función , la operación copiar / mover se puede omitir construyendo el objeto automático directamente en el valor de retorno de la función

[...]

Cuando se cumplen los criterios para elisión de una operación de copia y el objeto que se va a copiar se designa con un lvalor, la resolución de sobrecarga para seleccionar el constructor de la copia se realiza primero como si el objeto se designara con un valor r .

Tenga en cuenta que debe devolver un std::vector<T> ( por valor ), no un std::vector<T>&& ( por referencia ).

Pero, ¿por qué el paréntesis? return no es una función:

return theVector;


La forma estándar de mover algo es con std::move(x) , no con static_cast . AFAIK, la Optimización del Valor de Retorno Nombrado es probable que comience devolviendo un vector por valor, por lo que también se habría desempeñado antes de la semántica de movimiento.

Su prueba de rendimiento es una buena ilustración de cómo la semántica de movimiento es buena para el rendimiento: la copia-tarea tiene que copiar un millón de elementos, y la asignación de movimiento esencialmente solo intercambia los punteros internos del vector, que es una asignación de palabras triviales o dos. solo unos pocos ciclos.


Para agregar a la respuesta de GMan: incluso si cambia el tipo de retorno para que sea std::vector<T> (sin referencias, de lo contrario obtendrá UB), su cambio de expresión de retorno en "1)" nunca hará que el rendimiento mejor, pero podría empeorar un poco. Como std::vector tiene el constructor de movimiento, y usted devuelve un objeto local, no se llamará al constructor de copia del vector , no importa que usted haya escrito return theVector; , return static_cast<std::vector<T>&&>(theVector); , o return std::move(theVector) . En los dos últimos casos, el compilador se verá obligado a llamar al constructor de movimientos. Pero en el primer caso tiene la libertad de optimizar el movimiento por completo, si puede hacer NRVO para esa función. Si NRVO no es posible por alguna razón, solo entonces el compilador recurrirá a llamar al constructor de movimientos. Entonces no cambies el return x; para return std::move(x); si x es un objeto local no estático en la función que devuelve, de lo contrario evitará que el compilador use otra oportunidad de optimización.


Una referencia sigue siendo una referencia. De la misma manera que no puede devolver una referencia a un local en C ++ 03 (u obtiene UB), no puede hacerlo en C ++ 0x. Terminarás con una referencia a un objeto muerto; simplemente resulta ser una referencia de valor. Entonces esto está mal:

std::vector<T>&& doSomething() const { std::vector<T> local; return local; // oops return std::move(local); // also oops }

Deberías hacer lo que viste en el número dos:

// okay, return by-value std::vector<T> doSomething() const { std::vector<T> local; return local; // exactly the same as: return std::move(local); // move-construct value }

Las variables locales de una función son temporales cuando regresa, por lo que no es necesario cambiar el código. El tipo de devolución es el responsable de implementar la semántica de movimientos, no usted.

Desea usar std::move para mover algo de forma explícita , cuando no se haría normalmente, como en su prueba. (Lo cual parece estar bien, ¿eso fue en Release? Deberías generar los contenidos del vector, o el compilador lo optimizará).

Si quieres aprender sobre referencias de valores, lee esto .