Pregunta ¿Cuándo devolver un puntero, escalar y referencia en C ++?


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).

Tiendo a usar referencias siempre que sea posible, porque eso es lo más cercano posible a Java. En algunos casos, p. getters para atributos derivados, esto no es posible:

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

Esto no compila, porque t existe solo dentro del alcance de getSomeAttribute() y si devuelvo una referencia, no apuntaría a ninguna parte antes de que el cliente pueda usarla.

Por lo tanto, me quedan dos opciones:

  1. Devuelve un puntero
  2. Devuelve un escalar

Devolver un puntero se vería así:

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

Esto funcionaría, pero el cliente debería verificar este puntero para NULL para estar realmente seguro, algo que no es necesario con las 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 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?


32
2017-08-07 18:07


origen


Respuestas:


Regreso 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 ves 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 new por todo el lugar. 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 ha salido del alcance), y que puede hacer aritmética de puntero en un puntero en un formación. Una referencia de C ++ es un alias para un objeto. Una referencia de Java no se comporta de esa manera.


24
2017-08-07 18:11



Simplemente, evite el uso de punteros y asignación dinámica por new donde 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.


3
2017-08-07 18:12



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 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 miembro, y devuelvo algún tipo de objeto de puntero inteligente de algún tipo (frecuentemente ::std::auto_ptr) cuando necesito asignar dinámicamente algo. De lo contrario, regreso por valor.

También tengo muy frecuentemente 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 evitar una asignación dinámica siempre que sea posible es una buena idea. 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.


2
2017-08-07 18:20



Regresar 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 simple ejemplo 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
2017-08-07 18:19



Un punto relacionado con pasar por valor o referencia:
Teniendo en cuenta las optimizaciones, suponiendo que una función es en línea, si su parámetro se declara como "const DataType objectName" que DataType podría ser cualquier primitiva, no se incluirá ninguna copia de objeto; 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 las referencias:
Una referencia no siempre es un puntero, como una instancia cuando tiene un código siguiente en el cuerpo de una función, la referencia no es un puntero:

int adad=5;
int & reference=adad;  

Un punto con respecto a regresar por valor:
como algunas personas han mencionado, usar buenos compiladores con capacidad de optimización, devolver por valor de cualquier tipo no causará una copia adicional.

Un punto con respecto al retorno por referencia:
En caso de en línea funciones y optimizaciones, regresar por referencia no implicará toma de direcciones o puntero.


0
2017-08-07 20:10