La mayoría de las cosas en los lenguajes de programación no son completamente y totalmente gratis. A menos que esté escribiendo código en tiempo de compilación, es poco probable que escribir una función de identidad sea gratuito.
Vamos a retrabajo su código un poco:
#include <algorithm>
#include <iostream>
template <typename T>
T id1(T&& t)
{
return t;
}
template <typename T>
T id2(T&& t)
{
return std::move(t);
}
class X
{
public:
X()
{ output0("Xdef"); }
X(std::string const& s) : label_(s)
{ output1("Xstr",s); }
X(X const& x) : label_(x.label_)
{ output1("Xcopy", x); }
X(X&& x) : label_(std::move(x.label_))
{ output1("Xmove", x); }
X& operator =(X const& x)
{
output1("operator =copy", x);
label_ = x.label_;
return *this;
}
X& operator =(X&& x)
{
using std::swap;
output1("operator =move", x);
swap(label_, x.label_);
return *this;
}
~X()
{ output0("~X"); }
private:
void output_id() const
{
std::cout << this << '[' << label_ << "]";
}
void output0(std::string const& name) const
{
output_id();
std::cout << ": " << name << "()" << std::endl;
}
void output1(std::string const& name, std::string const& str) const
{
output_id();
std::cout
<< ": " << name
<< "(\"" << str
<< "\")" << std::endl;
}
void output1(std::string const& name, X const& arg) const
{
output_id();
std::cout << ": " << name << '(';
arg.output_id();
std::cout << ')' << std::endl;
}
std::string label_;
};
int main()
{
{
std::cout << "CASE A:\n";
auto x = X("x1");
}
std::cout << "\n";
{
std::cout << "CASE B:\n";
auto x = id1(X("x2"));
}
std::cout << "\n";
{
std::cout << "CASE C:\n";
auto x = id2(X("x3"));
}
std::cout << "\n";
{
std::cout << "CASE D:\n";
X x = id1(X("x4"));
}
std::cout << "\n";
{
std::cout << "CASE E:\n";
X x = id2(X("x5"));
}
}
y cuando ejecutarlo salidas (mediante una instantánea GCC v4.8):
$ ./a.out
CASE A:
0x7fff411fc530[x1]: Xstr("x1")
0x7fff411fc530[x1]: ~X()
CASE B:
0x7fff411fc540[x2]: Xstr("x2")
0x7fff411fc520[x2]: Xcopy(0x7fff411fc540[x2])
0x7fff411fc540[x2]: ~X()
0x7fff411fc520[x2]: ~X()
CASE C:
0x7fff411fc540[x3]: Xstr("x3")
0x7fff411fc520[x3]: Xmove(0x7fff411fc540[])
0x7fff411fc540[]: ~X()
0x7fff411fc520[x3]: ~X()
CASE D:
0x7fff411fc540[x4]: Xstr("x4")
0x7fff411fc520[x4]: Xcopy(0x7fff411fc540[x4])
0x7fff411fc540[x4]: ~X()
0x7fff411fc520[x4]: ~X()
CASE E:
0x7fff411fc540[x5]: Xstr("x5")
0x7fff411fc520[x5]: Xmove(0x7fff411fc540[])
0x7fff411fc540[]: ~X()
0x7fff411fc520[x5]: ~X()
$
Caso A simplemente invoca el constructor para X. El =
en este caso equivale a pasar el lado derecho del =
a X, es decir, no es una asignación.
Caso B invoca id1()
que no mueve su argumento de retorno. Como el valor devuelto no se definió en la pila de llamadas de id() y el valor es un valor l (que contiene un valor r), no se movió automáticamente en el momento del retorno y, por lo tanto, se copió.
El caso C invoca id2()
que invoca el constructor de movimiento a la vuelta.
Los Casos D y E son los mismos que los Casos B y C respectivamente, excepto que auto
no se usa si usted era escéptico al respecto.
Los movimientos deben verse como copias optimizadas y tan malas como las copias en el peor de los casos (aunque a menudo serán mucho mejores).Incluso un movimiento óptimo tiene un costo (por ejemplo, copiar algunos datos (normalmente) de un marco de pila a otro). La única forma en que se evitan por completo las copias/movimientos en el código de tiempo de ejecución es cuando la optimización del valor de retorno y ellison de copia son elegibles para el uso del compilador.
El problema es que una declaración de función no indica si la referencia devuelta aún hace referencia al mismo objeto que se le ha pasado. Y no es razonable forzar a los compiladores a verificar la implementación de la función para determinar exactamente qué devuelve y hacer que la vida temporal dependa de este análisis. – sellibitze