c++ - que - punteros a cadenas
¿Cuándo devolver un puntero, escalar y referencia en C++? (5)
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, por ejemplo getters para atributos derivados, esto no es posible:
MyType &MyClass::getSomeAttribute() {
MyType t;
return t;
}
Esto no se compila, porque t
existe solo dentro del alcance de getSomeAttribute()
y si devuelvo una referencia a él, no apunta a ninguna parte antes de que el cliente pueda usarlo.
Por lo tanto, me quedan dos opciones:
- Devuelve un puntero
- 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 tendría que marcar 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?
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 de 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 con frecuencia tengo parámetros de referencia const
, 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.
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.
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 así, por lo que llaman a alguien new
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 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.
Simplemente, evite utilizar 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.
Un punto relacionado con pasar por valor o referencia:
Teniendo en cuenta las optimizaciones, suponiendo que una función está en línea , si su parámetro se declara como "const DataType objectName" que DataType podría ser algo primitivo, 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 el caso de las funciones y optimizaciones en línea, la devolución por referencia no implicará la toma de direcciones o el puntero.