¿Por qué el operador de asignación de copia debe devolver una referencia/const?


En C++, el concepto de devolver la referencia del operador de asignación de copia no está claro para mí. ¿Por qué el operador de asignación de copia no puede devolver una copia del nuevo objeto? Además, si tengo clase A, y lo siguiente:

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

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

El operator= se define como sigue:

A A::operator=(const A& a)
{
    if (this == &a)
    {
        return *this;
    }
    param = a.param;
    return *this;
}
Author: Azeem, 2010-06-24

6 answers

Estrictamente hablando, el resultado de un operador de asignación de copia no necesita devolver una referencia, aunque para imitar el comportamiento predeterminado que usa el compilador de C++, debe devolver una referencia no-const al objeto al que se asigna (un operador de asignación de copia generado implícitamente devolverá una referencia no-const - C++03: 12.8/10). He visto un poco de código que devuelve void de sobrecargas de asignación de copia, y no puedo recordar cuando eso causó un problema grave. Volviendo {[0] } evitar que los usuarios 'encadenamiento de asignación' (a = b = c;), y evitará el uso del resultado de una asignación en una expresión de prueba, por ejemplo. Si bien ese tipo de código no es de ninguna manera inaudito, tampoco creo que sea particularmente común, especialmente para tipos no primitivos (a menos que la interfaz de una clase pretenda este tipo de pruebas, como para iostreams).

No estoy recomendando que hagas esto, solo señalando que está permitido y que no parece causar un montón de problema.

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

 57
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:32:26

Un poco de aclaración sobre por qué es preferible devolver por referencia para operator= versus devolver por valor --- ya que la cadena a = b = c funcionará 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 devuelve por valor para operator=, llamará a un constructor Y destructor CADA vez que se llame al operador de asignación.!

So, 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 suma, no hay nada que ganar al devolver por valor, sino mucho que perder.

(Nota: Esto no pretende abordar las ventajas de que el operador de asignación devuelva un lvalue. Leer los otros mensajes de por qué eso podría ser preferible)

 47
Author: Alex Collins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-20 10:02:01

Cuando sobrecarga operator=, puede escribirlo para devolver el tipo que desee. Si lo desea lo suficiente, puede sobrecargar X::operator= para devolver (por ejemplo) una instancia de una clase completamente diferente Y o Z. Esto es generalmente altamente desaconsejable sin embargo.

En particular, por lo general desea soportar el encadenamiento de operator= al igual que C lo hace. Por ejemplo:

int x, y, z;

x = y = z = 0;

Siendo ese el caso, generalmente desea devolver un lvalue o rvalue del tipo ser asignado a. Eso solo deja la cuestión de si devolver una referencia a X, una referencia const a X, o una X (por valor).

Devolver una referencia const a X es generalmente una mala idea. En particular, se permite que una referencia const se vincule a un objeto temporal. La vida de lo temporal se extiende a la vida de la referencia a la que está vinculado but pero no recursivamente a la vida de lo que sea que se le pueda asignar. Esto hace que sea fácil devolver un colgando referencia the la referencia const se vincula a un objeto temporal. La vida útil de ese objeto se extiende a la vida útil de la referencia (que termina al final de la función). En el momento en que la función regresa, la vida útil de la referencia y temporal han terminado, por lo que lo que se asigna es una referencia colgando.

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 duro en ello. Todavía se puede (por ejemplo) definir algunos locales, y devuelven una referencia a él (pero la mayoría de los compiladores pueden y advertirán sobre esto también).

Devolver un valor en lugar de una referencia tiene problemas teóricos y prácticos. En el lado teórico, usted tiene una desconexión básica entre = normalmente significa y lo que significa en este caso. En particular, donde la 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 fuente, cree una copia de ella y asigne ese valor a este destino existente."

Desde un punto de vista práctico, especialmente antes de que se inventaran las referencias rvalue, eso podría tener un impacto significativo en el rendimiento creating crear un objeto completamente nuevo en el curso de copiar 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 tomara, a lo sumo, tiempo para copiar elementos del vector pequeño más un (poco) fijo sobrecarga para ajustar el tamaño del vector de destino. Si eso implicara dos copias, una de la fuente a la temperatura, otra de la temperatura al destino, y (peor) una asignación dinámica para el vector temporal, mi expectativa sobre la complejidad de la operación sería completamente destruida. Para un vector pequeño, el tiempo para la asignación dinámica podría ser fácilmente muchas veces mayor que el tiempo para copiar los elementos.

La única otra opción (añadida en C++11) sería devolver una referencia rvalue. Esto podría conducir fácilmente a resultados inesperados a una asignación encadenada como a=b=c; podría destruir el contenido de b y/o c, lo que sería bastante inesperado.

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

 8
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-27 02:44:23

Es en parte porque devolver una referencia a uno mismo es más rápido que devolver por valor, pero además, es para permitir la semántica original que existe en los tipos primitivos.

 4
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-23 21:52:12

operator= se puede definir para devolver lo que quieras. Debe ser más específico en cuanto a cuál es el problema en realidad; sospecho que el constructor de copia usa operator= internamente y eso causa un desbordamiento de pila, ya que el constructor de copia llama a operator= que debe usar el constructor de copia para devolver A por valor ad infinitum.

 4
Author: MSN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-23 22:07:09

No hay ningún requisito de lenguaje central en el tipo de resultado de un operator= definido por el usuario, pero la biblioteca estándar tiene dicho requisito:

C++98 §23.1/3:

" El tipo de objetos almacenados en estos componentes debe cumplir los requisitos de CopyConstructible tipos (20.1.3), y los requisitos adicionales de los tipos Assignable.

C++98 §23.1/4:

" En el cuadro 64, T es el tipo utilizado para crear una instancia del contenedor, t es un valor de T, y u es un valor de (posiblemente const) T.

introduzca la descripción de la imagen aquí


Devolver una copia por valor todavía soportaría el encadenamiento de asignación como a = b = c = 42;, porque el operador de asignación es asociativo a la derecha, es decir, esto se analiza como a = (b = (c = 42));. Pero devolver una copia prohibiría construcciones sin sentido como (a = b) = 666;. Para una clase pequeña devolver una copia podría concebiblemente ser más eficiente, mientras que para una clase más grande devolver por referencia será en general, ser más eficiente (y una copia, prohibitivamente ineficiente).

Hasta que me enteré del requisito de la biblioteca estándar, solía dejar que operator= regresara void, por eficiencia y para evitar el absurdo de soportar código defectuoso basado en efectos secundarios.


Con C++11 existe adicionalmente el requisito de T& tipo de resultado para default - ing el operador de asignación, porque

C++11 §8.4.2/1:

" Una función que es explícitamente defaulted deberá []] tener el mismo tipo de función declarado (excepto posiblemente diferentes ref-qualifiers y excepto que en en el caso de un constructor de copia o operador de asignación de copia, el tipo de parámetro puede ser " referencia a non-const T", donde T es el nombre de la clase de la función miembro) como si se hubiera declarado implícitamente

 3
Author: Cheers and hth. - Alf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-18 02:30:35