2010-08-07 18 views
32

Me estoy moviendo de Java a C++ y estoy un poco confundido con la flexibilidad del idioma. Un punto es que hay tres formas de almacenar objetos: un puntero, una referencia y un escalar (almacenar el objeto si lo entiendo correctamente).¿Cuándo devolver un puntero, escalar y referencia en C++?

Tiendo a utilizar referencias siempre que sea posible, porque eso es lo más parecido posible a Java. En algunos casos, p. captadores de atributos derivados, esto no es posible:

MyType &MyClass::getSomeAttribute() { 
    MyType t; 
    return t; 
} 

Esto no compila, porque t existe sólo dentro del ámbito de getSomeAttribute() y si vuelvo una referencia a él, sería apuntar a ninguna parte antes de que el cliente puede usarlo .

Por lo tanto me quedo con dos opciones:

  1. devolver un puntero
  2. retornar un escalar

devolviendo un puntero se vería así:

MyType *MyClass::getSomeAttribute() { 
    MyType *t = new MyType; 
    return t; 
} 

Este trabajo, pero el cliente tendría que marcar este puntero para NULL para poder realmente seguro, algo que no es necesario con referencias. Otro problema es que la persona que llama debería asegurarse de que t está desasignado, prefiero no lidiar con eso si puedo evitarlo.

La alternativa sería la de devolver el objeto en sí (escalar):

MyType MyClass::getSomeAttribute() { 
    MyType t; 
    return t; 
} 

Eso es bastante sencillo y justo lo que quiero en este caso: Se siente como una referencia y no puede ser nulo. Si el objeto está fuera del alcance en el código del cliente, se elimina. Bastante práctico. Sin embargo, rara vez veo a alguien haciendo eso, ¿hay alguna razón para eso? ¿Hay algún tipo de problema de rendimiento si devuelvo un escalar en lugar de un puntero o referencia?

¿Cuál es el enfoque más común/elegante para manejar este problema?

+12

"Me estoy moviendo de Java a C++" no significa nada. Java no es C++. Java nunca será C++. Pensar en Java mientras se programa C++ no lo ayudará a comprender C++ ni a tomar buenas decisiones. Cualquier idioma no le ayudará a programar un buen C++, excepto C++, así que olvide que conoce otros idiomas, porque no son C++. Tienes que aprender C++ si quieres programar en C++, no tratar de transformar un lenguaje preexistente en C++, porque eso no funcionará, ya que solo C++ es C++. [¿Puedo sugerir un buen libro?] (Http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) – GManNickG

+3

Una referencia no está cerca de lo que hace Java. La construcción de lenguaje más cercana a Java sería devolver un puntero. Pero eso no tiene en cuenta la gestión de la memoria. Por lo tanto, la construcción de C++ más cercana a Java sería devolver un puntero compartido. std :: tr1 :: shared_ptr (o std :: shared_ptr o boost :: shared_ptr (dependiendo de la versión que esté usando)). Puede actualizar y olvidar el contenido de su corazón y, por lo general, el objeto será recogido correctamente. Pero C++ no es Java. Debe aprender a usar los valores correctamente. No traiga conceptos de Java a C++, los idiomas son simplemente diferentes. –

+6

@Gman - Ok, así que C++ no es Java, pero ignora los detalles del lenguaje. las ideas expresadas en Java se pueden traducir a C++. A menudo programo en varios idiomas, a menudo la sintaxis difiere pero no la semántica. – brumScouse

Respuesta

24

Vuelva por valor. El compilador puede optimizar la copia, por lo que el resultado final es lo que desea. Se crea un objeto y se devuelve a la persona que llama.

Creo que la razón por la que rara vez se ve a la gente hacer esto es porque estás viendo el código C++ incorrecto. ;) La mayoría de las personas que vienen de Java se sienten incómodas al hacer algo como esto, por lo que llaman al new en todas partes. Y luego tienen fugas de memoria por todo el lugar, tienen que verificar NULL y todos los otros problemas que pueden causar. :)

También podría valer la pena señalar que las referencias de C++ tienen muy poco en común con las referencias de Java. Una referencia en Java es mucho más similar a un puntero (puede volver a colocarse o establecerse en NULL). De hecho, las únicas diferencias reales son que un puntero también puede apuntar a un valor de basura (si no está inicializado, o apunta a un objeto que se ha salido del alcance), y que puede hacer aritmética de puntero en un puntero hacia una matriz. A C++ references es un alias para un objeto. Una referencia de Java no se comporta de esa manera.

+0

Gracias, ¿entonces no hay una advertencia real de devolver un atributo derivado por valor? Genial :) ¿Qué le parece devolver un miembro por valor en lugar de por referencia cómo lo hago actualmente? O pasando parámetros por valor. ¿Esto es ineficiente? – theone

+0

Hay advertencias prácticamente en todas partes en C++. Pero siempre que tenga en mente lo obvio, está devolviendo * una copia *, en lugar del objeto en sí, y siempre que sus constructores de copias estén escritos correctamente, debería estar bien. – jalf

+0

En cuanto a pasar parámetros por valor, depende. Una vez más, el compilador puede eliminar la copia (y normalmente lo hace de forma agresiva). Además, hay casos en que la copia puede ser * más rápida * (http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/). Pero a menudo, las personas pasan de referencia constante en lugar de confiar en el compilador para optimizar la copia. – jalf

3

En pocas palabras, evite el uso de punteros y asignación dinámica por new siempre que sea posible.Use valores, referencias y objetos asignados automáticamente en su lugar. Por supuesto, no siempre se puede evitar la asignación dinámica, pero debería ser el último recurso, no el primero.

0

Devolver por valor es algo común que se practica en C++. Sin embargo, cuando pasas un objeto, pasas por referencia.

Ejemplo

main() 
    { 

     equity trader; 

     isTraderAllowed(trader); 

     .... 
    } 

    bool isTraderAllowed(const equity& trdobj) 
    { 
      ... // Perform your function routine here. 
    } 

Lo anterior es un ejemplo sencillo de pasar un objeto por referencia. En realidad, tendrías un método llamado isTraderAllowed para el equity de la clase, pero te estaba mostrando un uso real de pasar por referencia.

+0

Este no es siempre el caso, a veces puede preferir pasar por valor – Puppy

2

Devolver por valor puede introducir penalizaciones de rendimiento porque esto significa que el objeto debe ser copiado. Si se trata de un objeto grande, como una lista, esa operación puede ser muy costosa.

Pero los compiladores modernos son muy buenos para hacer que esto no suceda. Los estándares de C++ establecen explícitamente que el compilador puede eliminar copias en ciertas circunstancias. La instancia particular que sería relevante en el código de ejemplo que dio se llama 'optimización del valor de retorno'.

Personalmente, regreso por referencia (generalmente const) cuando devuelvo una variable de miembro, y devuelvo algún tipo de objeto de puntero inteligente de algún tipo (con frecuencia ::std::auto_ptr) cuando necesito asignar dinámicamente algo. De lo contrario, regreso por valor.

También con mucha frecuencia tengo const parámetros de referencia, y esto es muy común en C++. Esta es una forma de pasar un parámetro y decir "la función no puede tocar esto". Básicamente es un parámetro de solo lectura. Sin embargo, solo debería usarse para objetos que son más complejos que un solo entero o puntero.

Creo que un gran cambio de Java es que const es importante y se usa con mucha frecuencia. Aprende a entenderlo y hazlo tu amigo.

También creo que la respuesta de Neil es correcta al afirmar que es una buena idea evitar la asignación dinámica siempre que sea posible. No debe contorsionar su diseño demasiado para que eso suceda, pero definitivamente debe preferir opciones de diseño en las que no deba suceder.

+0

Un gran consejo, gracias. Realmente me encanta const, no puedo creer cómo viví sin él. Sin embargo, todavía tengo problemas para decidir cuándo usarlo. Por ejemplo : Tengo una clase que encapsula las buenas llamadas de OpenGL procedurales. Si un método hace cosas OpenGL, ¿es const o no? Quiero decir, altera algún estado en algún lugar del programa. – theone

+0

@theone: el problema habitual es "¿altera el estado * lógico * del objeto del que es miembro?". Si me das un objeto const OpenGL, ¿qué operaciones tendría sentido para mí realizar? No se preocupe demasiado por si altera un poco el estado en alguna parte. – jalf

+0

@theone, @jalf - Añadiría que "alterar el estado lógico" debería tener una definición formal de "¿Altera el resultado semántico (es decir, podría alterar la cantidad de tiempo que toma, pero no lo que hace) de cualquier llamada subsiguiente a una función miembro del mismo objeto? ". Por supuesto, en ciertas circunstancias, 'const' tendrá una definición más estricta porque el estado del objeto puede estar en la memoria que no es grabable. – Omnifarious

0

Un punto con respecto a paso por valor o referencia:
optimizaciones Teniendo en cuenta, asumiendo una función es inline, si su parámetro se declara como "objectName DataType const" que DataType podría ser cualquier cosa, incluso primitivas, ninguna copia objeto será involucrado; y si su parámetro se declara como "const DataType & objectName" o "DataType & objectName" que nuevamente DataType podría ser cualquier primitiva, no se tomarán direcciones ni punteros. En ambos casos anteriores, los argumentos de entrada se usan directamente en el código de ensamblaje.

Un punto con respecto a referencias:
Una referencia no es siempre un indicador, como instancia cuando haya siguiente código en el cuerpo de una función, la referencia no es un puntero:

int adad=5; 
int & reference=adad; 

Un punto con respecto devolviendo por valor:
como algunas personas han mencionado, utilizando buenos compiladores con capacidad de optimizaciones, devolver por valor de cualquier tipo no causará una copia adicional.

Un punto sobre la devolución por referencia:
En caso de línea funciones y optimizaciones, volviendo por referencia no implicará tomar dirección o puntero.

+1

Todo lo audaz realmente le quita lo que está tratando de decir. –

Cuestiones relacionadas