c++ exception-handling assignment-operator exception-safety

c++ - ¿Qué hay de malo en "verificar la autoasignación" y qué significa?



exception-handling assignment-operator (4)

De la guía de c ++ core

Foo& Foo::operator=(const Foo& a) // OK, but there is a cost { if (this == &a) return *this; s = a.s; i = a.i; return *this; }

Esto es obviamente seguro y aparentemente eficiente. Sin embargo, ¿qué pasa si hacemos una autoasignación por millón de asignaciones ? Eso es alrededor de un millón de pruebas redundantes (pero como la respuesta es siempre la misma, el predictor de rama de la computadora siempre acertará). Considerar:

Foo& Foo::operator=(const Foo& a) // simpler, and probably much better { s = a.s; i = a.i; return *this; }

Nota: el código anterior solo se aplica a las clases sin puntero, para las clases con puntero que apuntan a la memoria dinámica. Por favor refiérase a la respuesta de Ant.

En el libro Exceptional C ++ de Herb Sutter (1999) , él tiene palabras en la solución del artículo 10:

"Excepción insegura" y "diseño deficiente" van de la mano. Si un fragmento de código no es seguro para las excepciones, generalmente está bien y puede ser simplemente reparado. Pero si un fragmento de código no se puede hacer a excepción de la excepción debido a su diseño subyacente, casi siempre es una señal de su diseño deficiente.

Ejemplo 1: Una función con dos responsabilidades diferentes es difícil de hacer a excepción de la seguridad.

Ejemplo 2: un operador de asignación de copia que está escrito de tal manera que debe verificar la autoasignación probablemente tampoco sea muy seguro de excepciones

¿Qué quiere decir con el término "verificar la autoasignación"?

[INVESTIGACIÓN]

Dave y AndreyT nos muestran exactamente lo que significa "verificar la autoasignación". Eso es bueno. Pero la pregunta no ha terminado. ¿Por qué "verificar la autoasignación" daña la "seguridad de excepción" (según Hurb Sutter)? Si la persona que llama intenta realizar la autoasignación, ese "chequeo" funciona como si no hubiera ninguna asignación. ¿Realmente duele?

[MEMO 1] En el punto 38 Identidad del objeto más adelante en el libro de Herb, él explica acerca de la autoasignación.


La pregunta más importante en este caso es qué significa "escrito de tal manera que deba verificar la autoasignación".

Esto significa que un operador de asignación bien diseñado no debería necesitar verificar la autoasignación. Asignar un objeto a sí mismo debería funcionar correctamente (es decir, tener el efecto final de "no hacer nada") sin realizar una verificación explícita de autoasignación.

Por ejemplo, si quisiera implementar una clase de matriz simplista a lo largo de las líneas de

class array { ... int *data; size_t n; };

y se le ocurrió la siguiente implementación del operador de asignación

array &array::operator =(const array &rhs) { delete[] data; n = rhs.n; data = new int[n]; std::copy_n(rhs.data, n, data); return *this; }

esa implementación se consideraría "mala" ya que obviamente falla en caso de autoasignación.

Para "arreglarlo" se puede agregar una comprobación de autoasignación explícita

array &array::operator =(const array &rhs) { if (&rhs != this) { delete[] data; n = rhs.n; data = new int[n]; std::copy_n(rhs.data, n, data); } return *this; }

o seguir un enfoque "sin cheques"

array &array::operator =(const array &rhs) { size_t new_n = rhs.n; int *new_data = new int[new_n]; std::copy_n(rhs.data, new_n, new_data); delete[] data; n = new_n; data = new_data; return *this; }

Este último enfoque es mejor en el sentido de que funciona correctamente en situaciones de autoasignación sin realizar una comprobación explícita de ello. (Esta implementación aún está lejos de ser perfecta desde el punto de vista de la seguridad de excepción, está aquí para ilustrar la diferencia entre los enfoques "verificados" y "sin cheques" para manejar la autoasignación). La última implementación sin verificación se puede escribir de forma más elegante a través del conocido lenguaje de copia e intercambio.

Esto no significa que deba evitar las comprobaciones explícitas de autoasignación. Dicha verificación tiene sentido desde el punto de vista del rendimiento : no tiene sentido realizar una larga secuencia de operaciones para terminar "sin hacer nada" al final. Pero en un operador de asignación bien diseñado tales controles no deberían ser necesarios desde el punto de vista de la corrección .


La razón general para verificar la autoasignación es porque destruye sus propios datos antes de copiar en uno nuevo. Esta estructura de operador de asignación tampoco es muy segura de excepciones.

Como anexo, se determinó que la autoasignación no beneficia en absoluto al rendimiento, ya que la comparación debe ejecutarse cada vez, pero la autoasignación es extremadamente rara, y si ocurre, este es un error lógico en su programa (realmente) . Esto significa que en el transcurso del programa, es solo una pérdida de ciclos.


MyClass& MyClass::operator=(const MyClass& other) // copy assignment operator { if(this != &other) // <-- self assignment check { // copy some stuff } return *this; }

Asignar un objeto a sí mismo es un error, pero no debería dar como resultado lógico el cambio de la instancia de su clase. Si logras diseñar una clase donde la asignación a sí misma la cambia, está mal diseñada.