2010-04-11 33 views
19

Ahora estoy realmente confundido sobre cómo y qué método usar para devolver el objeto de una función. Quiero retroalimentación sobre las soluciones para los requisitos dados.Devolver objeto desde la función

Escenario A: El objeto devuelto debe almacenarse en una variable que no necesita modificarse durante su vida útil. Por lo tanto,

const Foo SomeClass::GetFoo() { 
return Foo(); 
} 

invocarse como:

someMethod() { 
const Foo& l_Foo = someClassPInstance->GetFoo(); 
//... 
} 

Scneraio B: El objeto devuelto es para ser almacenado en una variable que se modifica durante su vida útil. Por lo tanto,

void SomeClass::GetFoo(Foo& a_Foo_ref) { 
    a_Foo_ref = Foo(); 
    } 

invocado como:

someMethod() { 
Foo l_Foo; 
someClassPInstance->GetFoo(l_Foo); 
//... 
} 

Tengo una pregunta aquí: Digamos que Foo no puede tener un constructor por defecto. Entonces ¿cómo hacer frente a que en esta situación, ya que puedo escribir esto más:

Foo l_Foo 

Escenario C:

Foo SomeClass::GetFoo() { 
return Foo(); 
} 

invocado como:

someMethod() { 
Foo l_Foo = someClassPInstance->GetFoo(); 
//... 
} 

pienso este no es el enfoque recomendado, ya que incurriría en la construcción de temporales adicionales.

¿Qué opinas? Además, ¿me recomiendan una mejor manera de manejar esto en su lugar?

+2

http://en.wikipedia.org/wiki/Return_value_optimization – vladr

+0

escenario A está bien, pero realmente no hay mejora con respecto a C.Usaría C a menos que el constructor predeterminado de Foo sea "rápido", la copiadora de Foo sea "lenta" y no quiera confiar en que el compilador sea lo suficientemente inteligente para eludir las copias innecesarias, en cuyo caso B también es aceptable. – sellibitze

+0

tenga en cuenta que 'a_Foo_ref = Foo();' crea un objeto y realiza una copia. Es casi equivalente al último escenario – Anycorn

Respuesta

16

En primer lugar, vamos a ver en las cosas que entran en juego aquí:

(a) La extensión de vida útil de un temporal cuando se usa para inicializar una referencia - Me enteré de ello en this publication por Andrei Anexandrescu. Una vez más, se siente raro pero útil:

class Foo { ... } 

Foo GetFoo() { return Foo(); } // returning temporary 

void UseGetFoo() 
{ 
    Foo const & foo = GetFoo(); 
    // ... rock'n'roll ... 
    foo.StillHere(); 
} 

La regla dice que cuando una referencia se inicializa con un temporal, la vida del temporal se extiende hasta la referencia se sale del ámbito. (this reply cotizaciones del canon)

(b) Valor de retorno Optimización - (wikipedia) - las dos copias locales -> valor de retorno -> local de puede omitirse en circunstancias. Esa es una regla sorprendente, ya que permite al compilador cambiar el comportamiento observable, pero es útil.

Ahí lo tienes. C++: extraño pero útil.


Así que mirar sus escenarios

Escenario A: va a devolver un temporal, y enlazarlo a una referencia - de por vida del temporal se extiende a toda la vida del l_Foo.

Tenga en cuenta que esto no funcionaría si GetFoo devolviera una referencia en lugar de una temporal.

Escenario B: Obras, excepto que las fuerzas un constructo-Construct-Copy-ciclo (que puede ser mucho más caro que el único constructo), y el problema que mencionas acerca requiere un constructor por defecto.

No utilizaría ese patrón para crear un objeto, solo para mutar uno existente.

Escenario C: Las copias de los temporales pueden ser omitidas por el compilador (a partir de la regla RVO). Desafortunadamente no hay garantía, pero los compiladores modernos sí implementan RVO.

Rvalue references en C++ 0x permite a Foo implementar un constructor de robo de recursos que no solo garantiza la supresión de las copias, sino que también resulta útil en otros escenarios.

(dudo que hay un compilador que implementa referencias rvalue pero no RVO. Sin embargo, hay escenarios en los RVO no puede patada en.)


una pregunta como ésta requiere mencionar punteros inteligentes, tales como shared_ptr y unique_ptr (este último es un "seguro" auto_ptr). También están en C++ 0x. Proporcionan un patrón alternativo para las funciones de creación de objetos.


+0

@peter: ¡Gracias por una respuesta tan elaborada con esos enlaces! Realmente lo aprecio. He estado tratando de resolver esto por bastante tiempo, así que estoy familiarizado con el Escenario A. Tuve 1 pregunta sobre el Escenario B. Usualmente uso B cuando tengo que modificar un objeto y no cuando tengo que crearlo dentro de GetFoo () Ahora, asumiendo eso; cuando llamamos a 'Foo l_Foo; someClassPInstance-> GetFoo (l_Foo); ':: Enviamos la referencia de l_Foo, por lo tanto no hay temporales aquí. Y luego, en 'void SomeClass :: GetFoo (Foo & a_Foo_ref) {// ..}' solo digamos, lo modifico y no lo creo. ¿Crees que B es una buena opción? – brainydexter

+0

Además, mira mi último comentario para la pregunta original. – brainydexter

+0

@brainydexter: Sí, ese es un escenario de uso típico para una referencia. Requiere cierta documentación (está fuera o dentro/fuera, etc.). Gracias por la referencia, actualizaré con un enlace. --- Recientemente he jugado con referencias de valor real, también fue un buen ejercicio para mí. – peterchen

4

De los tres escenarios, el número 3 es el método ideomático, y el que probablemente debería usar. No pagará por copias adicionales porque el compilador puede usar copy elision para evitar la copia si es posible.

Secnario A está equivocado. Usted termina con una referencia a un temporal que se destruye cuando finaliza la instrucción que contiene la llamada a la función. bien, el escenario A, no está mal, pero aún se debe utilizar el Escenario C.

Secnario B funciona bien, pero:

supongamos que Foo no puede tener un constructor por defecto. Entonces, ¿cómo lidiarías con eso en esta situación, ya que no podemos escribir esto más: Foo l_Foo.

Foo tiene que tener un constructor por defecto. Incluso si no le das uno, el compilador debe hacerlo por ti. Suponiendo que declare un constructor private, no hay forma de que pueda usar este método de llamada. Usted necesita hacer Foo editable por defecto o usar el Secnario C.

+1

'const Foo SomeClass :: GetFoo() ':: Escenario A :: No estoy devolviendo la referencia a un temporal aquí .. – brainydexter

+0

@brainydexter: Ups ... estás en lo cierto. Sin embargo, creo que todavía está mal. No estoy 100% seguro, pero creo que el temporal será destruido a pesar de tener una referencia. Necesitas tener el objeto real alrededor. –

+1

escenario A está bien, pero realmente no hay mejoría que C. Hay una regla especial en C++ que extiende la vida útil del objeto temporal en este caso. – sellibitze

Cuestiones relacionadas