sobrecarga sencillos relacionales operadores operador matrices ejemplos ejemplo c++ variable-assignment copy-constructor c++-faq

relacionales - sobrecarga de operadores en c++ ejemplos sencillos



Copiar constructor y=sobrecarga del operador en C++: ¿es posible una función común? (3)

Algo me molesta:

MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }

Primero, leer la palabra "intercambiar" cuando mi mente piensa "copiar" irrita mi sentido común. Además, cuestiono el objetivo de este truco elegante. Sí, cualquier excepción en la construcción de los recursos nuevos (copiados) debe ocurrir antes del intercambio, lo que parece una forma segura de asegurarse de que todos los datos nuevos se llenen antes de que se activen.

Esta bien. Entonces, ¿qué pasa con las excepciones que ocurren después del intercambio? (cuando los recursos antiguos se destruyen cuando el objeto temporal sale del alcance) Desde la perspectiva del usuario de la asignación, la operación ha fallado, excepto que no lo hizo. Tiene un enorme efecto secundario: la copia realmente sucedió. Solo fue algo de limpieza de recursos que falló. El estado del objeto de destino se ha modificado aunque la operación parece haber fallado desde el exterior.

Por lo tanto, propongo en lugar de "cambiar" para hacer una "transferencia" más natural:

MyClass& operator=(const MyClass& other) { MyClass tmp(other); transfer(tmp); return *this; }

Todavía existe la construcción del objeto temporal, pero la siguiente acción inmediata es liberar todos los recursos actuales del destino antes de moverlos (y NULL para que no se liberen dos veces) los recursos de la fuente.

En lugar de {construir, mover, destruir}, propongo {construir, destruir, mover}. El movimiento, que es la acción más peligrosa, es el que se toma el último después de que todo lo demás haya sido resuelto.

Sí, la destrucción falla es un problema en cualquiera de los esquemas. Los datos están corruptos (copiados cuando no lo creías) o perdidos (liberados cuando no creías que lo eran). Perdido es mejor que corrupto. No hay datos mejores que malos datos.

Transferencia en lugar de intercambio. Esa es mi sugerencia de todos modos.

Desde un constructor de copia

MyClass(const MyClass&);

y an = sobrecarga del operador

MyClass& operator = (const MyClass&);

tienen casi el mismo código, el mismo parámetro, y solo difieren en el retorno, ¿es posible tener una función común para ambos?


El constructor de copia realiza la inicialización por primera vez de los objetos que solían ser memoria en bruto. El operador de asignación, OTOH, anula los valores existentes con los nuevos. Con más frecuencia que nunca, esto implica descartar los recursos antiguos (por ejemplo, memoria) y asignar nuevos.

Si hay una similitud entre los dos, es que el operador de asignación realiza la destrucción y la construcción de copias. Algunos desarrolladores solían implementar la asignación por destrucción in situ seguida de la construcción de la copia de la ubicación. Sin embargo, esta es una muy mala idea. (¿Qué pasa si este es el operador de asignación de una clase base que llamó durante la asignación de una clase derivada?)

Lo que generalmente se considera la expresión canónica hoy en día es usar swap como Charles sugirió:

MyClass& operator=(MyClass other) { swap(other); return *this; }

Esto usa copia y construcción (tenga en cuenta que other se copia) y destrucción (se destruye al final de la función) y también los utiliza en el orden correcto: la construcción (puede fallar) antes de la destrucción (no debe fallar).


Sí. Hay dos opciones comunes. Uno, que generalmente se desaconseja, es llamar al operator= desde el constructor de copia de forma explícita:

MyClass(const MyClass& other) { operator=(other); }

Sin embargo, proporcionar un buen operator= es un desafío cuando se trata de lidiar con el estado anterior y los problemas que surgen de la autoasignación. Además, todos los miembros y las bases se inicializan por defecto primero, incluso si se asignan desde other . Esto ni siquiera puede ser válido para todos los miembros y bases, e incluso cuando es válido, es semánticamente redundante y puede ser prácticamente costoso.

Una solución cada vez más popular es implementar operator= utilizando el constructor de copia y un método de intercambio.

MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }

o incluso:

MyClass& operator=(MyClass other) { swap(other); return *this; }

Por lo general, una función de swap es simple de escribir, ya que solo cambia la propiedad de las partes internas y no tiene que limpiar el estado existente ni asignar nuevos recursos.

Las ventajas de la copia y el idioma de intercambio es que es automáticamente seguro para la auto asignación y, siempre que la operación de intercambio sea cero, también es una excepción segura.

Para garantizar la seguridad de las excepciones, un operador de asignación escrito a mano normalmente tiene que asignar una copia de los recursos nuevos antes de desasignar los recursos anteriores del asignante, de modo que si se produce una excepción asignando los recursos nuevos, el estado anterior aún puede devolverse a . Todo esto viene de forma gratuita con copiar y cambiar, pero por lo general es más complejo y, por lo tanto, propenso a errores, para hacer desde cero.

Lo que hay que tener cuidado es asegurarse de que el método de intercambio sea un true swap, y no el std::swap predeterminado, que utiliza el constructor de copias y el propio operador de asignación.

Normalmente, se utiliza un swap miembros. std::swap funciona y es ''no-throw'' garantizado con todos los tipos básicos y tipos de punteros. La mayoría de los punteros inteligentes también se pueden cambiar con una garantía de no-tiro.