¿Cómo “devolver un objeto” en C ++?

Sé que el título me suena familiar ya que hay muchas preguntas similares, pero estoy pidiendo un aspecto diferente del problema (sé la diferencia entre tener cosas en la stack y ponerlas en el montón).

En Java, siempre puedo devolver referencias a objetos “locales”

public Thing calculateThing() { Thing thing = new Thing(); // do calculations and modify thing return thing; } 

En C ++, para hacer algo similar tengo 2 opciones

(1) Puedo usar referencias cada vez que necesito “devolver” un objeto

 void calculateThing(Thing& thing) { // do calculations and modify thing } 

Entonces úsalo así

 Thing thing; calculateThing(thing); 

(2) O puedo devolver un puntero a un objeto dinámicamente asignado

 Thing* calculateThing() { Thing* thing(new Thing()); // do calculations and modify thing return thing; } 

Entonces úsalo así

 Thing* thing = calculateThing(); delete thing; 

Usando el primer enfoque no tendré que liberar memoria manualmente, pero para mí hace que el código sea difícil de leer. El problema con el segundo enfoque es: tendré que acordarme de delete thing; , que no se ve muy bien. No quiero devolver un valor copiado porque es ineficiente (creo), así que aquí vienen las preguntas

  • ¿Hay una tercera solución (que no requiere copiar el valor)?
  • ¿Hay algún problema si me apego a la primera solución?
  • ¿Cuándo y por qué debería usar la segunda solución?

No quiero devolver un valor copiado porque es ineficiente

Pruébalo.

Busque RVO y NRVO, y en C ++ 0x move-semántica. En la mayoría de los casos en C ++ 03, un parámetro de salida es solo una buena manera de hacer que tu código sea feo, y en C ++ 0x te estarías haciendo daño usando un parámetro de salida.

Simplemente escriba el código limpio, devuelva por valor. Si el rendimiento es un problema, perfíllo (deje de adivinar) y encuentre lo que puede hacer para solucionarlo. Es probable que no devuelva cosas de las funciones.


Dicho eso, si estás decidido a escribir así, probablemente quieras hacer el parámetro out. Evita la asignación de memoria dinámica, que es más segura y generalmente más rápida. Requiere que tenga alguna forma de construir el objeto antes de llamar a la función, lo que no siempre tiene sentido para todos los objetos.

Si desea utilizar la asignación dinámica, lo mínimo que se puede hacer es ponerlo en un puntero inteligente. (Esto debe hacerse todo el tiempo de todos modos). Entonces no se preocupe por borrar nada, las cosas son a prueba de excepciones, etc. ¡El único problema es que probablemente sea más lento que devolver el valor de todos modos!

Solo crea el objeto y devuélvelo

 Thing calculateThing() { Thing thing; // do calculations and modify thing return thing; } 

Creo que te harás un favor si te olvidas de la optimización y simplemente escribes un código legible (necesitarás ejecutar un generador de perfiles más tarde, pero no pre-optimizar).

Solo devuelve un objeto como este:

 Thing calculateThing() { Thing thing(); // do calculations and modify thing return thing; } 

Esto invocará al constructor de copias en Things, por lo que es posible que desee realizar su propia implementación de eso. Me gusta esto:

 Thing(const Thing& aThing) {} 

Esto podría funcionar un poco más lento, pero podría no ser un problema en absoluto.

Actualizar

El comstackdor probablemente optimizará la llamada al constructor de copia, por lo que no habrá gastos adicionales. (Como dreamlax señaló en el comentario).

¿Intentó utilizar punteros inteligentes (si Thing es realmente un objeto grande y pesado), como auto_ptr:

 std::auto_ptr calculateThing() { std::auto_ptr thing(new Thing); // .. some calculations return thing; } // ... { std::auto_ptr thing = calculateThing(); // working with thing // auto_ptr frees thing } 

Una forma rápida de determinar si se está llamando a un constructor de copia es agregar el registro al constructor de copia de su clase:

 MyClass::MyClass(const MyClass &other) { std::cout << "Copy constructor was called" << std::endl; } MyClass someFunction() { MyClass dummy; return dummy; } 

Llamar a someFunction ; el número de líneas que obtendrá del "Copiar constructor" variará entre 0, 1 y 2. Si no obtiene ninguno, entonces su comstackdor ha optimizado el valor de retorno (lo que está permitido hacer). Si obtienes no obtienes 0, y tu constructor de copia es ridículamente caro, entonces busca formas alternativas de devolver instancias de tus funciones.

En primer lugar, tiene un error en el código, quiere decir Thing *thing(new Thing()); , y solo return thing; .

  • Use shared_ptr . Deref como si fuera un puntero. Se borrará cuando la última referencia a la Thing contenida quede fuera del scope.
  • La primera solución es muy común en las bibliotecas ingenuas. Tiene cierto rendimiento y carga sintáctica, evítelo si es posible
  • Utilice la segunda solución solo si puede garantizar que no se lanzarán excepciones, o si el rendimiento es absolutamente crítico (se conectará con C o ensamblar antes de que esto llegue a ser relevante).

Estoy seguro de que un experto en C ++ obtendrá una mejor respuesta, pero personalmente me gusta el segundo enfoque. El uso de punteros inteligentes ayuda con el problema de olvidarse de delete y, como usted dice, se ve más limpio que tener que crear un objeto de antemano (y aún tener que eliminarlo si desea asignarlo en el montón).