Pregunta

Desde un constructor de copia

MyClass(const MyClass&);

y un = sobrecarga operador

MyClass& operator = (const MyClass&);

tienen más o menos el mismo código, el mismo parámetro, y sólo se diferencian en la vuelta, es posible tener una función común para los dos para usar?

¿Fue útil?

Solución

Sí. Hay dos opciones comunes. Uno - que en general se desaconseja - es llamar al operator= desde el constructor de copia explícitamente:

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

Sin embargo, proporcionar una buena operator= es un reto cuando se trata de lidiar con el viejo Estado y los problemas derivados de la asignación de auto. Además, todos los miembros y bases obtienen por defecto inicializa primero incluso si han de ser asignados a partir de other. Esto puede incluso no ser válida para todos los miembros y bases e incluso donde es válido semánticamente es redundante y puede ser prácticamente caro.

Una solución cada vez más popular es la de poner en práctica 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 función swap suele ser fácil de escribir, ya que sólo intercambia la propiedad de las partes internas y no tiene que limpiar el estado existente o asignar nuevos recursos.

Ventajas de la copia y de intercambio modismo es que es automáticamente seguro de auto-misiones y - siempre que la operación de intercambio no puede tiro - está también fuertemente excepción segura

.

Para ser fuertemente excepción caja fuerte, 'mano' operador de asignación escrita normalmente tiene que asignar una copia de los nuevos recursos antes de-la asignación de viejos recursos del cesionario de manera que si se produce una excepción asignar los nuevos recursos, el viejo estado todavía puede ser devuelto a. Todo esto viene de forma gratuita con el copia-y-swap pero típicamente es más complejo, y por lo tanto propenso a errores, para hacer a partir de cero.

La única cosa a tener cuidado es para asegurarse de que el método de intercambio es un verdadero intercambio, y no el std::swap por defecto que utiliza el constructor de copia y asignación propio operador.

se utiliza típicamente un swap miembro por miembro. std::swap funciona y es 'no-tiro' garantizada con todos los tipos básicos y los tipos de puntero. La mayoría de los punteros inteligentes también se pueden intercambiar con una garantía de no-tiro.

Otros consejos

El constructor de copia realiza primero-tiempo de inicialización de objetos que solían ser memoria prima. El operador de asignación, otoh, anula los valores existentes por otras nuevas. Más a menudo que no, se trata de despedir a los recursos de edad (por ejemplo, memoria) y la asignación de los nuevos.

Si hay una similitud entre los dos, es que el operador de asignación lleva a cabo la destrucción y la copia de la construcción. Algunos desarrolladores utilizan para aplicar en la práctica la asignación por la destrucción en el lugar seguida de la colocación de copia de la construcción. Sin embargo, este es un muy mala idea. (¿Y si este es el operador de asignación de una clase base que llama durante la asignación de una clase derivada?)

Lo que generalmente se considera el lenguaje canónico hoy en día se utiliza como swap sugirió Charles:

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

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

Algo me molesta de:

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

En primer lugar, la lectura de la palabra "intercambio" cuando mi mente está pensando "copia" irrita mi sentido común. Además, pongo en duda el objetivo de este truco de fantasía. Sí, cualquier excepción en la construcción de los nuevos recursos (copiados) deben suceder antes del canje, lo que parece ser una manera segura para asegurarse de que todos los nuevos datos se llena antes de que salga a la luz.

Eso está bien. Por lo tanto, ¿qué pasa con las excepciones que se producen después del canje? (Cuando los viejos recursos se destruyen cuando el objeto temporal se sale del ámbito) Desde la perspectiva del usuario de la tarea, la operación ha fallado, excepto que no lo hizo. Tiene un gran efecto secundario: la copia sucedió realmente. Fue sólo una pequeña limpieza de recursos que ha fallado. El estado del objeto de destino ha sido alterado a pesar de que la operación parece desde el exterior al haber fallado.

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

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

Hay todavía la construcción del objeto temporal, pero la siguiente acción inmediata es liberar todos los recursos actuales del destino antes de pasar (y anulación por lo que no serán de doble liberado) los recursos de la fuente a la misma.

En lugar de {construir, mover, destrucción}, {propongo construir, destrucción, mover}. La medida, que es la acción más peligrosa, es la tomada pasado después de todo lo demás ha sido resuelto.

Sí, la destrucción fallan es un problema en uno de los sistemas. Los datos se corrompe o bien (copiado cuando no creía que fuera) o se pierde (liberada cuando usted no pensó que era). Lost es mejor que dañado. No hay datos mejores que los malos datos.

Transfer lugar de intercambio. Esa es mi sugerencia de todos modos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top