tuple c++ c++11 stdtuple rvo

tuple c++



Valor de retorno optimizado de tuple/tie (4)

Estoy estudiando la optimización del valor de retorno en el caso de tuple / ties y el comportamiento que observo no es el que esperaba. En el siguiente ejemplo, esperaría que se activara la semántica de movimiento, lo que ocurre, pero hay una operación de copia que permanece. La salida de la siguiente en optimizado es:

Test duo output, non_reference tuple Default constructor invoked Parameter constructor invoked Copy constructor invoked Move Assignment operator invoked 100

La invocación del constructor de copia para hacer la tupla dentro de la función parece innecesaria. ¿Hay alguna manera de eliminar esto? Estoy usando el compilador MSVC 2012.

#include <iostream> #include <tuple> class A { public: int value; A() : value(-1) { std::cout << "Default constructor invoked" << std::endl; } explicit A(const int v) : value(v) { std::cout << "Parameter constructor invoked" << std::endl; } A(const A& rhs) { value = rhs.value; std::cout << "Copy constructor invoked" << std::endl; } A(const A&& rhs) { value = rhs.value; std::cout << "Move constructor invoked" << std::endl; } A& operator=(const A& rhs) { value = rhs.value; std::cout << "Assignment operator invoked" << std::endl; return *this; } A& operator=(const A&& rhs) { value = rhs.value; std::cout << "Move Assignment operator invoked" << std::endl; return *this; } }; std::tuple<A, int> return_two_non_reference_tuple() { A tmp(100); return std::make_tuple(tmp, 99); } int main(int argc, char* argv[]) { std::cout << "Test duo output, non_reference tuple" << std::endl; A t3; int v1; std::tie(t3, v1) = return_two_non_reference_tuple(); std::cout << t3.value << std::endl << std::endl; system("pause"); return 0; }


El constructor de movimiento no se llamará automáticamente porque estás llamando

std::make_tuple(tmp, 99);

En este caso, tmp es un lvalue. Puede usar std::move para convertirlo en una referencia rvalue:

return std::make_tuple(std::move(tmp), 99);

Esto le indicará al compilador que use el constructor de movimiento.


La copia se produce aquí:

std::make_tuple(tmp, 99);

Aunque puede ver que tmp puede construirse directamente en la tupla, la copia de tmp a la tupla no será eliminada. Lo que realmente desea es una forma de pasar los argumentos para que std::tuple utilice para construir sus objetos internos A e int . No hay tal cosa para std::tuple , pero hay una manera de lograr el mismo efecto.

Como solo tienes dos tipos, puedes usar std::pair . Esto tiene un constructor std::piecewise_construct , que toma dos std::tuples contienen los argumentos para pasar a los constructores de los objetos internos.

std::pair<A, int> return_two_non_reference_tuple() { return {std::piecewise_construct, std::make_tuple(100), std::make_tuple(99)}; }

Lo bueno de esta solución es que aún puede usar std::tie en el sitio de la llamada, porque std::tuple tiene un operador de asignación de std::pair .

std::tie(t3, v1) = return_two_non_reference_tuple();

Como se puede ver en la salida, su copia se ha ido. No se reemplaza por un movimiento como en las otras respuestas, se elimina por completo:

Prueba dúo de salida, tupla de no referencia

Constructor por defecto invocado

Constructor de parámetros invocado

Operador de asignación de movimiento invocado

100

Demo en vivo


La copia tiene lugar en return_two_non_reference_tuple() .

Una forma de eliminarlo es mover tmp

std::tuple<A, int> return_two_non_reference_tuple() { A tmp(100); return std::make_tuple(std::move(tmp), 99); }

o de otra manera

std::tuple<A, int> return_two_non_reference_tuple() { return std::make_tuple(A(100), 99); }


No te estas moviendo tmp :

A tmp(100); return std::make_tuple(std::move(tmp), 99);