retornan - C++: devolviendo por referencia y copia los constructores
punteros c++ (9)
Básicamente, devolver una referencia solo tiene sentido si el objeto aún existe después de abandonar el método. El compilador le avisará si devuelve una referencia a algo que está siendo destruido.
Devolver una referencia en lugar de un objeto por valor guarda la copia del objeto que podría ser importante.
Las referencias son más seguras que los punteros porque tienen simulación diferente, pero detrás de escena son punteros.
Las referencias en C ++ me desconciertan. :)
La idea básica es que estoy intentando devolver un objeto desde una función. Me gustaría hacerlo sin devolver un puntero (porque entonces tendré que delete
manualmente), y sin llamar al constructor de copias, si es posible (para mayor eficiencia, naturalmente, y también porque me pregunto si puedo hacerlo) t evitar escribir un constructor de copia).
Así que, en general, aquí están las opciones para hacer esto que he encontrado:
- El tipo de retorno de la función puede ser la propia clase (
MyClass fun() { ... }
) o una referencia a la clase (MyClass& fun() { ... }
). - La función puede construir la variable en la línea de retorno (
return MyClass(a,b,c);
) o devolver una variable existente (MyClass x(a,b,c); return x;
). - El código que recibe la variable también puede tener una variable de cualquiera de los dos tipos: (
MyClass x = fun();
oMyClass& x = fun();
) - El código que recibe la variable puede crear una nueva variable sobre la marcha (
MyClass x = fun();
) o asignarla a una variable existente (MyClass x; x = fun();
)
Y algunos pensamientos sobre eso:
- Parece ser una mala idea tener el tipo de retorno
MyClass&
porque eso siempre provoca que la variable se destruya antes de que se devuelva. - El constructor de copia solo parece involucrarse cuando devuelvo una variable existente. Al devolver una variable construida en la línea de retorno, nunca se llama.
- Cuando asigno el resultado a una variable existente, el destructor también se activa antes de que se devuelva el valor. Además, no se llama a ningún constructor de copia, pero la variable de destino recibe los valores de miembro del objeto devuelto por la función.
Estos resultados son tan inconsistentes que me siento totalmente confundido. Entonces, ¿qué está pasando EXACTAMENTE aquí? ¿Cómo debo construir y devolver correctamente un objeto desde una función?
Estás atascado con cualquiera
1) devolviendo un puntero
MyClass * func () {// some stuf return new MyClass (a, b, c); }
2) devolviendo una copia del objeto MyClass func () {return MyClass (a, b, c); }
Devolver una referencia no es válido porque el objeto se destruirá después de salir del ámbito de la función, excepto si la función es miembro de la clase y la referencia es de una variable que es miembro de la clase.
La única vez que tiene sentido devolver una referencia es si está devolviendo una referencia a un objeto preexistente. Para un ejemplo obvio, casi todas las funciones miembro de iostream devuelven una referencia a iostream. El propio iostream existe antes de que se llame a cualquiera de las funciones miembro, y continúa existiendo después de que sean llamadas.
El estándar permite "copiar elision", lo que significa que no es necesario llamar al constructor de copia cuando devuelve un objeto. Esto viene en dos formas: Optimización de valor de retorno de nombre (NRVO) y Optimización de valor de retorno anónimo (generalmente solo RVO).
Por lo que está diciendo, su compilador implementa RVO pero no NRVO, lo que significa que probablemente sea un compilador algo más antiguo. La mayoría de los compiladores actuales implementan ambos. El dtor no emparejado en este caso significa que probablemente sea algo como gcc 3.4 o por ahí, aunque no recuerdo la versión, había una versión que tenía un error como este. Por supuesto, también es posible que su instrumentación no esté del todo bien, por lo que se está utilizando un ctor que usted no instrumentó, y se está invocando un dtor correspondiente para ese objeto.
Sin embargo, al final, te quedas atascado con un hecho simple: si necesitas devolver un objeto, debes devolver un objeto. En particular, una referencia solo puede dar acceso a un objeto existente (versión posiblemente modificada), pero ese objeto también tuvo que construirse en algún punto. Si puede modificar algún objeto existente sin causar un problema, está bien, hágalo. Si necesita un nuevo objeto diferente y separado de los que ya tiene, siga adelante y haga eso: crear previamente el objeto y pasarle una referencia puede hacer que el retorno sea más rápido, pero no ahorrará tiempo en general. Crear el objeto tiene aproximadamente el mismo costo, ya sea que se realice dentro o fuera de la función. Cualquier compilador razonablemente moderno incluirá RVO, por lo que no pagará ningún costo adicional por crearlo en la función y luego devolverlo. El compilador simplemente automatizará la asignación de espacio para el objeto al que se va a devolver, y tendrá la función constrúyalo "en el lugar", donde seguirá siendo accesible después de que la función regrese.
La mejor manera de entender la copia en C ++ es a menudo NO intentar producir un ejemplo artificial e instrumentarlo: el compilador tiene permiso para eliminar y agregar llamadas de constructor de copia, más o menos, según lo considere conveniente.
Línea inferior: si necesita devolver un valor, devuélvalo y no se preocupe por ningún "gasto".
Lectura recomendada: Efectivo C ++ por Scott Meyers. Encontrarás una muy buena explicación sobre este tema (y mucho más) allí.
En resumen, si regresa por valor, el constructor de copia y el destructor estarán involucrados de manera predeterminada (a menos que el compilador los optimice, eso es lo que sucede en algunos de sus casos).
Si devuelve por referencia (o puntero) una variable que es local (construida en la pila), invitará problemas porque el objeto se destruye al regresar, por lo tanto, como resultado, tendrá una referencia pendiente.
La forma canónica de construir un objeto en una función y devolverlo es por valor, como:
MyClass fun() {
return MyClass(a, b, c);
}
MyClass x = fun();
Si usa esto, no necesita preocuparse por los problemas de propiedad, referencias colgantes, etc. Y el compilador probablemente optimizará las llamadas adicionales de constructor / destructor para usted, por lo que tampoco tendrá que preocuparse por el rendimiento.
Es posible devolver por referencia un objeto construido por new
(es decir, en el montón); este objeto no se destruirá al regresar de la función. Sin embargo, tienes que destruirlo explícitamente en algún lugar más tarde llamando a delete
.
También es técnicamente posible almacenar un objeto devuelto por valor en una referencia, como:
MyClass& x = fun();
Sin embargo, AFAIK no tiene mucho sentido hacer esto. Especialmente porque uno puede pasar fácilmente esta referencia a otras partes del programa que están fuera del alcance actual; sin embargo, el objeto al que hace referencia x
es un objeto local que se destruirá tan pronto como abandone el ámbito actual. Así que este estilo puede llevar a insectos desagradables.
No es una respuesta directa, sino una sugerencia viable: también puede devolver un puntero, envuelto en un auto_ptr o smart_ptr. Entonces tendrás el control de cómo se llaman los constructores y los destructores y cuándo.
Si creas un objeto como este:
MyClass foo(a, b, c);
entonces estará en la pila en el marco de la función. Cuando esa función termina, su marco se desprende de la pila y todos los objetos en ese marco se destruyen. No hay forma de evitar esto.
Entonces, si desea devolver un objeto a una persona que llama, solo las opciones son:
- Devolución por valor: se requiere un constructor de copia (pero la llamada al constructor de copia puede optimizarse).
- Devuelva un puntero y asegúrese de utilizar punteros inteligentes para tratar con él o elimínelo con cuidado cuando termine.
Intentar construir un objeto local y luego devolver una referencia a esa memoria local a un contexto de llamada no es coherente: un ámbito de llamada no puede acceder a la memoria que es local al ámbito de llamada. Esa memoria local solo es válida por la duración de la función que la posee, o, de otra manera, mientras la ejecución permanezca en ese ámbito. Debes entender esto para programar en C ++.
Una posible solución, dependiendo de su caso de uso, es construir por defecto el objeto fuera de la función, tomar una referencia a él e inicializar el objeto referenciado dentro de la función, de este modo:
void initFoo(Foo& foo)
{
foo.setN(3);
foo.setBar("bar");
// ... etc ...
}
int main()
{
Foo foo;
initFoo(foo);
return 0;
}
Ahora, por supuesto, esto no funciona si no es posible (o no tiene sentido) construir un objeto Foo
por defecto y luego inicializarlo. Si ese es el caso, entonces su única opción real para evitar la construcción de copias es devolver un puntero a un objeto asignado en el montón.
Pero luego piense por qué está intentando evitar la copia de la construcción en primer lugar. ¿El "costo" de la construcción de copias realmente afecta su programa, o es este un caso de optimización prematura?
lea sobre RVO y NRVO (en una palabra, estos dos representan la Optimización del valor de retorno y el RVO nombrado, y son técnicas de optimización utilizadas por el compilador para hacer lo que usted está tratando de lograr)
Encontrarás muchos temas aquí en