¿Por qué el operador de asignación de copia debe devolver una referencia/referencia constante? [duplicar]

Resuelto bks asked hace 14 años • 8 respuestas

En C++, el concepto de devolver una referencia desde el operador de asignación de copia no me queda claro. ¿Por qué el operador de asignación de copia no puede devolver una copia del nuevo objeto? Además si tengo clase Ay lo siguiente:

A a1(param);
A a2 = a1;
A a3;

a3 = a2; //<--- this is the problematic line

El operator=se define de la siguiente manera:

A A::operator=(const A& a)
{
    if (this == &a)
    {
        return *this;
    }
    param = a.param;
    return *this;
}
bks avatar Jun 24 '10 04:06 bks
Aceptado

Una pequeña aclaración de por qué es preferible devolver por referencia en lugar operator=de devolver por valor, ya que la cadena a = b = cfuncionará bien si se devuelve un valor.

Si devuelve una referencia, se realiza un trabajo mínimo. Los valores de un objeto se copian a otro objeto.

Sin embargo, si regresa por valor para operator=, llamará a un constructor Y a un destructor CADA vez que se llame al operador de asignación.

Entonces, dado:

A& operator=(const A& rhs) { /* ... */ };

Entonces,

a = b = c; // calls assignment operator above twice. Nice and simple.

Pero,

A operator=(const A& rhs) { /* ... */ };

a = b = c; // calls assignment operator twice, calls copy constructor twice, calls destructor type to delete the temporary values! Very wasteful and nothing gained!

En resumen, no se gana nada regresando por valor, pero sí hay mucho que perder.

( Nota : esto no pretende abordar las ventajas de que el operador de asignación devuelva un valor l. Lea las otras publicaciones para saber por qué podría ser preferible)

Alex Collins avatar Jan 23 '2011 01:01 Alex Collins

Estrictamente hablando, el resultado de un operador de asignación de copia no necesita devolver una referencia, aunque para imitar el comportamiento predeterminado que utiliza el compilador de C++, debería devolver una referencia no constante al objeto al que está asignado (una copia generada implícitamente). El operador de asignación devolverá una referencia no constante (C++03: 12.8/10). He visto una buena cantidad de código que regresa voidde sobrecargas de tareas de copia y no recuerdo cuándo eso causó un problema grave. La devolución voidevitará que los usuarios 'encadenen tareas' ( a = b = c;) y evitará usar el resultado de una tarea en una expresión de prueba, por ejemplo. Si bien ese tipo de código no es desconocido, tampoco creo que sea particularmente común, especialmente para tipos no primitivos (a menos que la interfaz de una clase esté destinada a este tipo de pruebas, como iostreams).

No recomiendo que hagas esto, solo te señalo que está permitido y que no parece causar muchos problemas.

Estas otras preguntas SO están relacionadas (probablemente no del todo engañadas) y contienen información/opiniones que podrían ser de su interés.

  • ¿Alguien ha encontrado la necesidad de declarar constante el parámetro de retorno de un operador de asignación de copia?
  • Operador de asignación de sobrecarga en C++
Michael Burr avatar Jun 23 '2010 23:06 Michael Burr

Cuando lo sobrecarga operator=, puede escribirlo para devolver el tipo que desee. Si lo desea con todas sus fuerzas, puede sobrecargar X::operator=para devolver (por ejemplo) una instancia de alguna clase Yo archivo Z. Sin embargo , esto generalmente es muy desaconsejable.

En particular, normalmente desea admitir el encadenamiento de operator=tal como lo hace C. Por ejemplo:

int x, y, z;

x = y = z = 0;

Siendo ese el caso, normalmente querrás devolver un valor l o rvalue del tipo al que se le asigna. Eso solo deja la cuestión de si se debe devolver una referencia a X, una referencia constante a X o una X (por valor).

Devolver una referencia constante a X es generalmente una mala idea. En particular, se permite que una referencia constante se vincule a un objeto temporal. La vida útil del temporal se extiende a la vida útil de la referencia a la que está vinculado, pero no de forma recursiva a la vida útil de lo que sea que pueda estar asignado. Esto hace que sea fácil devolver una referencia pendiente: la referencia constante se vincula a un objeto temporal. La vida útil de ese objeto se extiende a la vida útil de la referencia (que finaliza al final de la función). Para cuando la función regresa, la vida útil de la referencia y del temporal ha finalizado, por lo que lo que se asigna es una referencia pendiente.

Por supuesto, devolver una referencia no constante no proporciona una protección completa contra esto, pero al menos te hace trabajar un poco más en ello. Aún puede (por ejemplo) definir algo local y devolverle una referencia (pero la mayoría de los compiladores también pueden advertir sobre esto y lo harán).

Devolver un valor en lugar de una referencia tiene problemas tanto teóricos como prácticos. Desde el punto de vista teórico, existe una desconexión básica entre =lo que normalmente significa y lo que significa en este caso. En particular, donde asignación normalmente significa "tomar esta fuente existente y asignar su valor a este destino existente", comienza a significar algo más como "tomar esta fuente existente, crear una copia de ella y asignar ese valor a este destino existente". "

Desde un punto de vista práctico, especialmente antes de que se inventaran las referencias de valores, eso podría tener un impacto significativo en el rendimiento: crear un objeto completamente nuevo durante la copia de A a B era inesperado y, a menudo, bastante lento. Si, por ejemplo, tuviera un vector pequeño y lo asignara a un vector más grande, esperaría que me tomara, como máximo, tiempo para copiar elementos del vector pequeño más una (pequeña) sobrecarga fija para ajustar el tamaño de el vector de destino. Si, en cambio, eso implicara dos copias, una del origen al temporal, otra del temporal al destino y (peor) una asignación dinámica para el vector temporal, mis expectativas sobre la complejidad de la operación quedarían completamente destruidas. Para un vector pequeño, el tiempo para la asignación dinámica podría fácilmente ser muchas veces mayor que el tiempo para copiar los elementos.

La única otra opción (agregada en C++ 11) sería devolver una referencia de valor. Esto podría conducir fácilmente a resultados inesperados: una tarea encadenada como a=b=c;podría destruir el contenido de by/o c, lo cual sería bastante inesperado.

Eso deja devolver una referencia normal (no una referencia a constante, ni una referencia a rvalue) como la única opción que (razonablemente) produce de manera confiable lo que la mayoría de la gente normalmente quiere.

Jerry Coffin avatar Nov 18 '2014 02:11 Jerry Coffin