2012-07-08 10 views
9

Estoy aprendiendo C++ desde un fondo de C y Java principalmente y tengo curiosidad sobre cuál es la mejor manera de devolver un objeto en C++ sin tener que copiar el objeto. Desde mi punto de vista, C++ 11 introdujo referencias rvalue (& &) para mover datos desde una variable temporal (como opuesta a la copia). Ejemplo:Devolución de un objeto en C++

std::string getStr(){ 
    return "Hello"; 
} 

std::string &&hello = getStr(); 

Otra forma en que podría pensar es en usar un puntero compartido.

std::tr1::shared_ptr<std::string> getStr(){ 

    std::tr1::shared_ptr<std::string> hello(new std::string("Hello")); 
    return hello; 

} 

auto hello = getStr(); 

estoy pensando que tal vez una referencia rvalue es mejor, pero me gustaría una segunda opinión antes de que lo uso. ¿Cual es mejor?

¿También se recomienda que los campos de una clase sean referencias de valor si no se configuran con el constructor? Ejemplo:

class StringHolder{ 

    public: 
    std::string &&str; 
}; 

StringHolder sh; 
sh.str = getStr(); 

¡Muchas gracias muchachos!

+1

Puede encontrar [Una breve introducción a las referencias de Rvalue] (http://www.artima.com/cppsource/rvalue.html) útil –

+0

Las implementaciones modernas de la biblioteca estándar utilizarán la optimización de cadenas pequeñas, por lo que dinámicamente la asignación de un la cadena "Hola" podría ser más lenta que simplemente devolver una por valor. – bames53

Respuesta

10

Esta pregunta probablemente se cerrará como un duplicado. Es una pregunta frecuente. Sin embargo, me gustaría responder de todos modos.

En C++ 11, cuando usted es el cliente de un objeto como std::string, debe pasar a su alrededor por su valor y no preocuparse tanto por la eficiencia:

std::string getStr(){ 
    return "Hello"; 
} 

std::string hello = getStr(); 

En este nivel no es necesario preocuparse por las referencias de rvalue Simplemente sepa que std::string puede "copiar" desde valores r (como el resultado de getStr()) muy de manera eficiente. Esta "copia" se llama en realidad "mover" y se habilita automáticamente porque está copiando desde un valor r (un temporal anónimo).

No intente optimizar la copia volviendo al recuento de referencias. Utilice únicamente el recuento de referencias si necesita semántica de propiedad compartida. Esta afirmación no es siempre verdadero. Pero para aprender lo básico, está lo suficientemente cerca como para ser una buena regla práctica.

lo necesario para empezar a preocuparse acerca de las referencias rvalue al diseñar la clase que necesita ser pasado alrededor por valor:

class Widget 
{ 
    // pointer to heap data 
public: 
    // ... 
}; 

Para una breve introducción a RValue referencias y mover la semántica, N2027 es un tutorial decente. N2027 es demasiado largo para pegarlo aquí, pero lo suficientemente corto para ser fácil de leer. std::string sigue los principios básicos establecidos en N2027 para permitirte pasarlo por alto sin sentir culpa. Y puede seguir esos mismos patrones de diseño en su Widget.

+0

¡Muchas gracias por esto! Poco a poco estoy comprendiendo el tutorial y el concepto de constructores de movimientos.¿La mayoría de los objetos en std tienen el constructor de movimiento como un vector (es decir, ¿es inteligente devolver un vector grande por valor)? Además, en su ejemplo de clase de widget, ese es el comentario "puntero a montón de datos" específico para este ejemplo o es más común usar punteros como campos en lugar de tipos de valor (aunque, inferiría el uso de un puntero o el valor del campo depende de la situación)? – DanB91

+0

Además, sé que esto depende del constructor de movimiento del objeto, pero supongo que, como regla general, si tiene obj o = std :: move (p) ;, probablemente nunca vuelva a usarse, ¿correcto? – DanB91

+0

@ DanB91: No hasta que esté asignado, de todos modos. – ildjarn

1

Las referencias de valores R serán mucho más rápidas, ya que son una función de lenguaje intrínseco, en lugar de una clase de recuento de referencias. Usar un puntero inteligente o compartido aquí no dará ningún beneficio y disminuirá la claridad. Sin embargo, las referencias rvalue son una parte del lenguaje diseñado para este tipo de cosas. Use la referencia rvalue.

2

A menudo puede evitar copias devolviendo una referencia constante a una variable miembro. Por ejemplo:

class Circle { 
    Point center; 
    float radius; 
public: 
    const Point & getCenter() const { return center; }; 
}; 

Esto es equivalente a return &center en ejecución y, de hecho, es probable que se inlined simplemente resulta en acceso directo (sólo lectura) del miembro de la persona que llama.

En los casos en que construye un temporal para devolver el compilador puede elide la copia adicional como una optimización, que no requiere una sintaxis especial de su parte.

0

Si desea utilizar el puntero inteligente en este caso, quizás unique_ptr es una mejor elección.

2

Por defecto, simplemente debe pasar cosas por valor. Si una función no necesita modificar o copiar un parámetro, se puede tomar por const&, pero simplemente tome los parámetros por valor. Devuelve objetos por valor y déjalo mover semántica o RVO para evitar una copia.


C++ utiliza y fomenta el paso de objetos 'por valor'. La semántica de movimiento es una función que permite que el código haga esto mientras hace menos copias que antes de que se introdujeran. Normalmente, el código no necesita usar tipos de referencia de valor r directamente para obtener el beneficio de la semántica de movimiento. Esto se debe a que los objetos temporales son valores r y la resolución de sobrecarga seleccionará automáticamente los métodos que tomen referencias rvalue.

Por ejemplo:

std::string getStr(){ 
    return "Hello"; 
} 

std::string hello = getStr(); 

Este código utiliza mover semántica. return "Hello" construye una cadena temporal y, por lo tanto, el constructor utilizado para inicializar el objeto de valor de retorno será el constructor de movimiento (aunque en realidad la optimización del valor de retorno evitará este movimiento). Entonces el valor devuelto por getStr() es temporal, por lo que el constructor seleccionado para string hello es nuevamente el constructor de movimientos.

Creo que también puede hacer std::string &&hello = getStr();, pero probablemente no sea necesario ya que hello ya se moverá construido.

Otra manera de que pudiera pensar en está utilizando un puntero compartida

No creo punteros inteligentes son una generalmente una buena idea a menos que realmente necesita la semántica de propiedad específicos que proporcionan. Usar un pase directo de 'valor por valor' es una buena opción predeterminada y la semántica de movimiento generalmente le permite usarlo sin copiarlo extra.

¿También se recomienda que los campos de una clase sean referencias de valores si no se establecerán con el constructor?

No, las referencias de los miembros no son una buena idea. Hacen las cosas más complicadas y ni siquiera se les permite usarlas como muestra su ejemplo. Si su código de ejemplo fue permitido, entonces sh.str = getStr(); sería un comportamiento indefinido, porque está tratando de asignar a un objeto inexistente. Es como std::string *str; *str = getStr();.

+0

Por lo que he reunido, pensé que las referencias rvalue salvan los valores devueltos de ser destruidos. De hecho, escribí una rápida "std :: string && hello = getStr();" programa y funcionó bien (en lugar de hacerlo string * hello; * hello = getStr() ;, que obviamente causó un error seg). Pero ahora estoy comprendiendo lentamente el uso de && y seguiré el consejo de devolver objetos por valor (siempre que sean pequeños o implementen el constructor de movimientos). ¡Gracias! – DanB91

+0

@ DanB91 En realidad, creo que tienes razón en que puedes capturar un valor de retorno con una referencia de valor real. – bames53

Cuestiones relacionadas