Pregunta

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ía que hacerlo manualmente delete it), y sin llamar al constructor de copias, si es posible (para mayor eficiencia, naturalmente agregado: y también porque me pregunto si no puedo evitar escribir un constructor de copias).

Entonces, en general, aquí están las opciones para hacer esto que he encontrado:

  • El tipo de retorno de la función puede ser la clase misma (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 cualquier tipo:(MyClass x = fun(); o MyClass& x = fun();)
  • El código que recibe la variable puede crear una nueva variable sobre la marcha (MyClass x = fun();) o asignarlo a una variable existente (MyClass x; x = fun();)

Y algunas reflexiones sobre eso:

  • Parece una mala idea tener el tipo de devolución. MyClass& porque eso siempre da como resultado que la variable se destruya antes de ser devuelta.
  • El constructor de copias sólo parece intervenir cuando devuelvo una variable existente.Cuando se devuelve una variable construida en la línea de retorno, nunca se llama.
  • Cuando asigno el resultado a una variable existente, el destructor también siempre 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?

¿Fue útil?

Solución

La mejor manera de entender el copiado en C ++ menudo no es tratar de producir un ejemplo artificial e instrumento que - el compilador se permite que tanto remover y añadir llamadas de constructor de copia, más o menos como lo considere oportuno.

En pocas palabras -. Si usted necesita devolver un valor, devolver un valor y no se preocupe por cualquier "gasto"

Otros consejos

Lectura recomendada: efectiva C ++ Scott Meyers. A encontrar una muy buena explicación acerca de este tema (y mucho más) en ese país.

En resumen, si regresa por valor, el constructor de copia y el destructor estarán involucrados de forma predeterminada (a menos que el compilador de los optimiza distancia - eso es lo que ocurre en algunos de sus casos).

Si regresa por referencia (o puntero) una variable que es local (construido en la pila), que genera problemas porque el objeto se destruye a su regreso, por lo que tiene una referencia que cuelga como resultado.

La forma canónica para construir un objeto en una función y volver es por valor, como:

MyClass fun() {
    return MyClass(a, b, c);
}

MyClass x = fun();

Si utiliza esto, no es necesario preocuparse por problemas de propiedad, etc. referencias pendientes y el compilador lo más probable es optimizar las llamadas copia extra del constructor / destructor para usted, por lo que no tiene que preocuparse acerca el rendimiento tampoco.

Es posible volver por referencia un objeto construido por new (es decir, en el montón) - este objeto no será destruida al regresar de la función. Sin embargo, usted tiene que destruir explícitamente en algún lugar más tarde llamando delete.

También es técnicamente posible almacenar un objeto devuelto por valor en una referencia, como:

MyClass& x = fun();

Sin embargo, que yo sepa no hay mucha razón para hacer esto. Sobre todo porque se puede pasar fácilmente en esta referencia a otras partes del programa que están fuera del ámbito de aplicación actual; sin embargo, el objeto referenciado por x es un objeto local que será destruida tan pronto como salga del ámbito actual. Por lo que este estilo puede conducir a errores desagradables.

RVO y NRVO (en una palabra estos dos soportes para la optimización del valor devuelto y con nombre RVO, y son técnicas de optimización utilizados por el compilador para hacer lo que estamos tratando de lograr)

encontrará una gran cantidad de temas aquí en stackoverflow

Si crea un objeto como éste:

MyClass foo(a, b, c);

entonces será en la pila en el marco de la función. Cuando termina esa función, su marco se extrae de la pila y todos los objetos de esa trama se destruyen. No hay manera de evitar esto.

Así que si usted quiere devolver un objeto a una persona que llama, sólo opciones son:

  • Vuelta por el valor -. Se requiere un constructor de copia (pero la llamada al constructor de copia puede ser optimizado a cabo)
  • Devuelve un puntero y asegurarse de que o bien utiliza punteros inteligentes para tratar con él o borrarlo mismo cuidado cuando termine con él.

El intento de construir un objeto local y luego devolver una referencia a la memoria local a un contexto de llamada no es coherente - un ámbito de aplicación que llama no puede acceder a la memoria que es local en el ámbito llamada. Esa memoria local sólo es válido para la duración de la función que lo posee - o, de otra manera, mientras que la ejecución se mantiene en ese ámbito. Debe entender esto a programar en C ++.

La única vez que tiene sentido devolver una referencia es si devuelve una referencia a un objeto preexistente.Por ejemplo, casi todas las funciones miembro de iostream devuelven una referencia a iostream.El iostream en sí existe antes de que se llame a cualquiera de las funciones miembro y continúa existiendo después de que se llame.

El estándar permite la "elisión de copia", lo que significa que no es necesario llamar al constructor de copia cuando se devuelve un objeto.Esto viene en dos formas:Nombre Optimización del valor de retorno (NRVO) y Optimización del valor de retorno anónimo (normalmente solo RVO).

Por lo que estás diciendo, tu compilador implementa RVO pero no NRVO, lo que significa que es probablemente un compilador algo más antiguo.La mayoría de los compiladores actuales implementan ambos.El dtor no coincidente en este caso significa que probablemente sea algo así como gcc 3.4 o algo así; aunque no recuerdo la versión con seguridad, había una que tenía un error como este.Por supuesto, también es posible que su instrumentación no sea del todo correcta, por lo que se está utilizando un ctor que usted no instrumentó y se invoca un dtor coincidente para ese objeto.

Al final, te quedas atrapado con un hecho simple:si necesita devolver un objeto, debe devolver un objeto.En particular, una referencia sólo puede dar acceso a una versión (posiblemente modificada) de un objeto existente, pero ese objeto también tuvo que construirse en algún momento.Si puedes modificar algún objeto existente sin causar un problema, está bien, adelante y hazlo.Si necesita un nuevo objeto diferente y separado de los que ya tiene, continúe y hágalo; crear previamente el objeto y pasarle una referencia puede hacer que la devolución sea más rápida, pero no ahorrará tiempo en general.Crear el objeto tiene aproximadamente el mismo costo ya sea que se haga 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 devolverá y tendrá la función. constrúyalo "en su lugar", donde aún será accesible después de que regrese la función.

Básicamente, devolviendo una referencia sólo tiene sentido si el objeto sigue existiendo después de abandonar el método. El compilador le advertirá si devuelve una referencia a algo que está siendo destruido.

Al regresar una referencia en lugar de un objeto por valor ahorra copiar el objeto que pudiera ser significativa.

Las referencias son más seguros que los punteros porque tienen diferentes symantics, pero detrás de las escenas que son punteros.

Una posible solución, dependiendo de su caso de uso, es por defecto-construir el objeto fuera de la función, tomar en una referencia a él, e inicializar el objeto referenciado dentro de la función, al igual que :

void initFoo(Foo& foo) 
{
  foo.setN(3);
  foo.setBar("bar");
  // ... etc ...
}

int main() 
{
  Foo foo;
  initFoo(foo);

  return 0;
}

Ahora bien, esto por supuesto no funciona si no es posible (o no tiene sentido) a los valores predeterminados-construir un objeto Foo y luego inicializar más tarde. Si ese es el caso, entonces su única opción real para evitar la copia de la construcción es devolver un puntero a un objeto heap-asignado.

Pero luego pensar acerca de por qué usted está tratando de evitar la copia de la construcción en el primer lugar. Es el "gasto" de la construcción copia realmente afecta a su programa, o se trata de un caso de la optimización prematura?

Se le Stucked con:

1) devolviendo un puntero

MiClase * func () {   // algunos stuf   volver nuevos MyClass (a, b, c); }

2) devolver una copia del objeto func MyClass () {   volver MyClass (a, b, c); }

Devolución de una referencia no es válido porque el objeto ha de ser destruida después de salir del alcance func, excepto si la función es un miembro de la clase y la referencia es de una variable que es miembro de la clase.

No es una respuesta directa, sino una sugerencia viable: También puede devolver un puntero, envuelto en una auto_ptr o smart_ptr. Entonces vas a estar en control de lo que los constructores y destructores las llaman y cuándo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top