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
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);