2010-07-28 37 views
131

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 pila y ponerlas en el montón).¿Cómo "devolver un objeto" en C++?

En Java siempre puede volver referencias a "local" objetos

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

En C++, hacer algo similar tengo 2 opciones

(1) Puedo utilizar referencias siempre que necesito " retorno" un objeto

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

luego usarlo como esto

Thing thing; 
calculateThing(thing); 

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

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

Entonces utilizar de esta manera

Thing* thing = calculateThing(); 
delete thing; 

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

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

+1 por muy bien plantear la pregunta. – Kangkan

+0

Para ser muy pedante, es un poco impreciso decir que "las funciones devuelven algo". Más correctamente, * la evaluación de una llamada de función produce un valor *. El valor siempre es un objeto (a menos que sea una función vacía).La distinción es si el valor es un glvalue o un prvalue, que está determinado por si declarado * return type * es una referencia o no. –

Respuesta

94

que no quieren devolver un valor copiado porque es ineficiente

probarlo.

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 de limpieza, 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 esto, si está decidido a escribir así, probablemente quiera 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!

+0

Gracias GMan, estoy estudiando C++ y leí en alguna parte que al devolver un valor, el valor tendrá que ser copiado a otro lugar y la copia parece ineficaz, ¿no? – phunehehe

+9

@phunehehe: No tiene sentido especular, debe perfilar su código y descubrirlo. (Sugerencia: no.) Los compiladores son muy inteligentes, no perderán el tiempo copiando cosas si no es necesario. * Incluso si * copiar cuesta algo, todavía debe esforzarse por un buen código sobre código rápido; un buen código es fácil de optimizar cuando la velocidad se convierte en un problema. No tiene sentido enarcar el código para algo que no tienes idea es un problema; especialmente si realmente lo desaceleras o no sacas nada de eso. Y si está usando C++ 0x, la semántica de movimiento lo hace no problemático. – GManNickG

+0

@phunehehe esto también puede ser útil en algunos casos, http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ – Anycorn

0

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

1

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

  • Use shared_ptr<Thing>. Deref como si fuera un puntero. Se borrará cuando la última referencia al Thing contenido quede fuera del alcance.
  • La primera solución es muy común en las bibliotecas ingenuas. Tiene algo de rendimiento, y gastos generales sintácticos, evítelo si es posible
  • Utilice la segunda solución solo si puede garantizar que no se emitirán excepciones, o si el rendimiento es absolutamente crítico (se conectará con C o ensamblar antes de que esto se convierta pertinente).
+0

gracias Matt, he solucionado la sintaxis – phunehehe

36

Basta con crear el objeto y devolverlo

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

creo que va a hacer usted mismo un favor si te olvidas de optimización y acaba de escribir código legible (que necesita para funcionar un generador de perfiles más adelante - pero no pre-optimizar).

+1

'Cosa cosa();' declara una función local y devuelve un 'Thing'. – dreamlax

+1

Cosa() declara que una función devuelve algo. No hay ningún objeto Thing construido en su cuerpo de función. –

+2

@dream @Charles: Hivemind? :) – GManNickG

11

¿trató de utilizar punteros inteligentes (si la cosa es muy grande y pesado objeto), como auto_ptr:


std::auto_ptr<Thing> calculateThing() 
{ 
    std::auto_ptr<Thing> thing(new Thing); 
    // .. some calculations 
    return thing; 
} 


// ... 
{ 
    std::auto_ptr<Thing> thing = calculateThing(); 
    // working with thing 

    // auto_ptr frees thing 
} 
+2

'auto_ptr's están en desuso; use 'shared_ptr' o' unique_ptr' en su lugar. – MBraedley

6

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

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

MyClass someFunction() 
{ 
    MyClass dummy; 
    return dummy; 
} 

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

10

sólo devuelve un objeto de la siguiente manera:

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

Esto invocará el constructor de copia sobre qué, por lo que es posible que desee hacer su propia aplicación de esa. De esta manera:

Thing(const Thing& aThing) {} 

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

actualización

El compilador probablemente optimizar la llamada al constructor de copia, por lo que habrá una carga extra. (Como dreamlax señaló en el comentario).

+7

'Cosa();' declara una función local que devuelve una 'Cosa', también, el estándar permite al compilador omitir el constructor de copia en el caso que usted presentó; cualquier compilador moderno probablemente lo haga. – dreamlax

+0

@dreamlax: De acuerdo –

+0

Trae un buen punto para implementar el constructor de copias, especialmente si se necesita una copia profunda. – mbadawi23

Cuestiones relacionadas