¿Por qué el constructor de movimiento definido por el usuario deshabilita el constructor de copia implícito?

Mientras leo boost / shared_ptr.hpp, vi este código:

// generated copy constructor, destructor are fine... #if defined( BOOST_HAS_RVALUE_REFS ) // ... except in C++0x, move disables the implicit copy shared_ptr( shared_ptr const & r ): px( r.px ), pn( r.pn ) // never throws { } #endif 

¿Qué significa el comentario “generado constructor de copia, destructor están bien, excepto en C ++ 11, mover desactiva la copia implícita” significa aquí? ¿Siempre debemos escribir el copiador nosotros mismos para evitar esta situación en C ++ 11?

He actualizado la respuesta de ildjarn porque la encontré precisa y divertida. 🙂

Proporciono una respuesta alternativa porque asumo por el título de la pregunta que el OP podría querer saber por qué el estándar así lo dice.

fondo

C ++ ha generado implícitamente miembros de copia porque si no fuera así, habría nacido muerto en 1985 porque era tan incompatible con C. Y en ese caso no estaríamos teniendo esta conversación hoy porque C ++ no existiría .

Dicho esto, los miembros copiados generados implícitamente son similares a un “trato con el demonio”. C ++ no podría haber nacido sin ellos. Pero son malvados porque silenciosamente generan código incorrecto en un número significativo de instancias. El comité C ++ no es estúpido, ellos lo saben.

C ++ 11

Ahora que C ++ ha nacido y se ha convertido en un adulto exitoso, al comité le encantaría decir: ya no estamos haciendo copias de los miembros generados implícitamente. Ellos son muy peligrosos. Si desea un miembro de copia generado implícitamente, debe optar por esa decisión (en lugar de optar por no participar). Sin embargo, considerando la cantidad de código C ++ existente que se rompería si esto se hiciera, eso equivaldría al suicidio. Existe una gran preocupación de compatibilidad con versiones anteriores que está bastante justificada.

Entonces, el comité llegó a una posición de compromiso: si declara mover miembros (lo que el código heredado de C ++ no puede hacer), entonces vamos a suponer que es probable que los miembros de la copia predeterminada hagan lo incorrecto. Opt-in (con =default ) si lo desea. O escríbelos tú mismo. De lo contrario, se eliminan implícitamente. Nuestra experiencia hasta la fecha en un mundo con tipos solo de movimiento indica que esta posición predeterminada es en realidad lo que se desea con frecuencia (por ejemplo, ofstream , ofstream , future , etc.). Y el gasto de opting-in es realmente bastante pequeño con = default .

Viendo hacia adelante

Al comité le encantaría incluso decir: si ha escrito un destructor, es probable que los miembros de la copia implícita sean incorrectos, por lo que los eliminaremos. Esta es la “regla de tres” de C ++ 98/03. Sin embargo, incluso eso rompería un montón de código. Sin embargo, el comité ha dicho en C ++ 11 que si proporciona un destructor declarado por el usuario, la generación implícita de los miembros de la copia queda obsoleta . Eso significa que esta característica podría eliminarse en un futuro estándar. Y que en cualquier momento su comstackdor podría comenzar a emitir “advertencias desaprobadas” en esta situación (el estándar no puede especificar advertencias).

Conclusión

Así que prevenido: C ++ ha crecido y madurado a lo largo de las décadas. Y eso significa que es posible que la C ++ de su padre necesite migrar para ocuparse del C ++ de su hijo. Es un proceso lento y gradual para que no arroje sus manos y solo transmita a otro idioma. Pero es cambio, aunque sea lento.

Porque el estándar de C ++ lo dice – §12.8 / 7:

Si la definición de clase no declara explícitamente un constructor de copia, se declara implícitamente . Si la definición de clase declara un constructor de movimiento o un operador de asignación de movimiento, el constructor de copia declarado implícitamente se define como eliminado ; de lo contrario, se define como incumplido. El último caso está en desuso si la clase tiene un operador de asignación de copia declarado por el usuario o un destructor declarado por el usuario. Por lo tanto, para la definición de clase

 struct X { X(const X&, int); }; 

un constructor de copia está implícitamente declarado. Si el constructor declarado por el usuario se define más tarde como

 X::X(const X& x, int i =0) { /* ... */ } 

entonces cualquier uso del constructor de copias de X está mal formado debido a la ambigüedad; no se requiere diagnóstico.

(Énfasis mío)