Desde un constructor de copias
MyClass(const MyClass&);
y una = sobrecarga del operador
MyClass& operator = (const MyClass&);
tienen prácticamente el mismo código, el mismo parámetro y solo difieren en la devolución, ¿es posible tener una función común para que ambos la usen?
c++
variable-assignment
copy-constructor
c++-faq
MPelletier
fuente
fuente
Respuestas:
Si. Hay dos opciones comunes. Uno, que generalmente no se recomienda, es llamar
operator=
explícitamente desde el constructor de copia:MyClass(const MyClass& other) { operator=(other); }
Sin embargo, proporcionar un bien
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 bases se inicializan por defecto primero, incluso si se les va a asignar desdeother
. Esto puede que ni siquiera sea válido para todos los miembros y bases e incluso cuando es válido es semánticamente redundante y puede resultar prácticamente caro.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; }
Una
swap
función suele ser sencilla de escribir, ya que simplemente intercambia la propiedad de los componentes internos y no tiene que limpiar el estado existente ni asignar nuevos recursos.Las ventajas del lenguaje de copiar e intercambiar es que es automáticamente seguro para la autoasignación y, siempre que la operación de intercambio sea sin tirar, también es muy seguro para excepciones.
Para estar fuertemente seguro de excepciones, un operador de asignación escrita 'a mano' generalmente tiene que asignar una copia de los nuevos recursos antes de desasignar los recursos antiguos del cesionario, de modo que si ocurre una excepción al asignar los nuevos recursos, el estado anterior aún se puede volver a . Todo esto viene gratis con copiar e intercambiar, pero generalmente es más complejo y, por lo tanto, propenso a errores, para hacerlo desde cero.
Lo único que debe tener cuidado es asegurarse de que el método de intercambio sea un intercambio verdadero, y no el predeterminado
std::swap
que usa el constructor de copia y el operador de asignación en sí.Normalmente
swap
se utiliza un miembro .std::swap
funciona y está garantizado 'no-throw' con todos los tipos básicos y tipos de puntero. La mayoría de los punteros inteligentes también se pueden intercambiar con una garantía de no tirar.fuente
operator=
la copia del ctor es de hecho bastante mala, porque primero inicializa todos los valores a algunos valores predeterminados solo para anularlos con los valores del otro objeto justo después.assign
función miembro utilizada tanto por el ctor de copia como por el operador de asignación en algunos casos (para clases ligeras). En otros casos (casos de uso intensivo de recursos / manejo / cuerpo) una copia / intercambio es el camino a seguir, por supuesto.El constructor de copia realiza la inicialización por primera vez de objetos que solían ser memoria sin formato. El operador de asignación, OTOH, reemplaza los valores existentes por otros nuevos. La mayoría de las veces, esto implica descartar 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 mediante destrucción in situ seguida de construcción de copia de 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 el idioma canónico hoy en día es usar
swap
como sugirió Charles:MyClass& operator=(MyClass other) { swap(other); return *this; }
Esto usa construcción de copia (tenga en cuenta que
other
se copia) y destrucción (se destruye al final de la función), y también los usa en el orden correcto: construcción (puede fallar) antes de la destrucción (no debe fallar).fuente
swap
declararsevirtual
?Algo me molesta por:
MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }
Primero, leer la palabra "intercambio" cuando mi mente está pensando en "copiar" irrita mi sentido común. Además, cuestiono el objetivo de este elegante truco. Sí, cualquier excepción en la construcción de los nuevos recursos (copiados) debe ocurrir antes del intercambio, lo que parece una forma segura de asegurarse de que se completen todos los datos nuevos antes de ponerlos en funcionamiento.
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 efecto secundario enorme: la copia realmente sucedió. Fue solo una limpieza de recursos la que falló. El estado del objeto de destino se ha modificado aunque la operación parece haber fallado desde el exterior.
Entonces, propongo en lugar de "swap" 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 mover (y anular para que no se liberen dos veces) los recursos de la fuente hacia él.
En lugar de {construir, mover, destruir}, propongo {construir, destruir, mover}. El movimiento, que es la acción más peligrosa, es el último que se toma después de que todo lo demás se ha resuelto.
Sí, la destrucción fallida es un problema en cualquier esquema. Los datos están dañados (copiados cuando no pensaba que lo estaban) o perdidos (liberados cuando no pensaba que lo estaban). Perdido es mejor que corrompido. Ningún dato es mejor que un dato incorrecto.
Transferir en lugar de intercambiar. De todos modos, esa es mi sugerencia.
fuente
First, reading the word "swap" when my mind is thinking "copy" irritates
-> Como escritor de una biblioteca, normalmente conoce las prácticas habituales (copiar + intercambiar), y el quid de la cuestión esmy mind
. Tu mente está realmente oculta detrás de la interfaz pública. De eso se trata el código reutilizable.