2011-08-23 27 views
7

lectura this respuesta relacionada con la devolución de referencias rvalue de la función me hizo pensar, ¿cómo puedo escribir una función id en C++ 0x.función "id" en C++ 0x

Básicamente, quiero que id sea una función de no hacer nada, una función que no tiene efectos observables en el programa.

Mi primer intento es el siguiente:

#include <iostream> 

class X 
{ 
public: 
    X(std::string&& s) : s(std::move(s)) {}; 
    X(const std::string& s) : s(s) {}; 
    std::string s; 
    ~X() { std::cout << "Destroying: " << s << std::endl; } 
private: 
    X(const X&) {}; 
    X(X&&) {}; 
}; 

template <class T> 
T&& id(T&& x) { return static_cast<T&&>(x); } 

int main() 
{ 
    auto&& x1 = X("x1"); 
    std::cout << "Line 1" << std::endl; 
    auto&& x2 = id(X("x2")); 
    std::cout << "Line 2" << std::endl; 
} 

Sin embargo, me temo que en este caso, x2 es una referencia colgando, como X("x2") se destruye antes de "Línea 2" se ejecuta.

Así que aquí, bastante claramente id tiene un efecto observable.

¿Cómo puedo escribir una función id en C++ 0x, que en particular funciona para los tipos sin mover/copiar los constructores.

+0

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

Respuesta

7

No puede. Como regla, no debe escribir funciones que devuelvan referencias rvalue, y como señaló correctamente, no puede prolongar el tiempo de vida del temporal lo suficiente.

0

Lo que se quiere hacer es llamado reenvío perfecto, y hay una función en la STL que lo hace:

template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept 
{ 
    return static_cast<T&&>(t) 
} 
template <class T> T&& forward(typename remove_reference<T>::type&& t) noexcept 
{ 
    return static_cast<T&&>(t) 
} 

Se necesita el remove_reference para evitar el colapso referencia. Y cuando se la usa, usted tiene que especificar el tipo de objeto que está intentando reenviar:

std::forward<X>(X("x2")); 
+1

Creo que es una especificación antigua y no lo que es actualmente estándar. Además, * no * resuelve el problema. – Puppy

0

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.