Con funciones de miembro explícitamente eliminadas en C ++ 11, ¿vale la pena heredar de una clase base no copiable?

Con funciones de miembro explícitamente eliminadas en C ++ 11, ¿vale la pena heredar de una clase base no copiable?

Estoy hablando del truco en el que heredas de forma privada una clase base que tiene una copia privada o una copia del constructor y la copia (por ejemplo, boost::noncopyable ).

¿Las ventajas presentadas en esta pregunta siguen siendo aplicables a C ++ 11?


No entiendo por qué algunas personas dicen que es más fácil hacer que una clase no se pueda copiar en C ++ 11.

En C ++ 03:

 private: MyClass(const MyClass&) {} MyClass& operator=(const MyClass&) {} 

En C ++ 11:

 MyClass(const MyClass&) = delete; MyClass& operator=(const MyClass&) = delete; 

EDITAR:

Como muchas personas han señalado, fue un error proporcionar cuerpos vacíos (es decir {}) para el constructor de copias privadas y el operador de asignación de copias, porque eso permitiría que la clase invocara esos operadores sin ningún error. Primero comencé sin agregar el {}, pero encontré algunos problemas con el enlazador que me hicieron agregar el {} por alguna razón tonta (no recuerdo las circunstancias). Sé que es mejor saberlo. 🙂

Bien, esto:

 private: MyClass(const MyClass&) {} MyClass& operator=(const MyClass&) {} 

Todavía técnicamente permite que MyClass sea ​​copiado por miembros y amigos. Claro, esos tipos y funciones teóricamente están bajo tu control, pero la clase aún se puede copiar . Al menos con boost::noncopyable y = delete , nadie puede copiar la clase.


No entiendo por qué algunas personas dicen que es más fácil hacer que una clase no se pueda copiar en C ++ 11.

No es tanto “más fácil” como “más fácil de digerir”.

Considera esto:

 class MyClass { private: MyClass(const MyClass&) {} MyClass& operator=(const MyClass&) {} }; 

Si usted es un progtwigdor de C ++ que ha leído un texto introductorio en C ++, pero tiene poca exposición a C ++ idiomático (es decir, un montón de progtwigdores de C ++), esto es … confuso. Declara constructores de copia y operadores de asignación de copias, pero están vacíos. Entonces, ¿por qué declararlos? Sí, son private , pero eso solo genera más preguntas: ¿por qué hacerlos privados?

Para comprender por qué esto impide la copia, debe tener en cuenta que al declararlos como privados, lo hace para que los que no sean miembros / amigos no puedan copiarlo. Esto no es inmediatamente obvio para el novato. Tampoco es el mensaje de error que obtendrán cuando intenten copiarlo.

Ahora, compárelo con la versión C ++ 11:

 class MyClass { public: MyClass(const MyClass&) = delete; MyClass& operator=(const MyClass&) = delete; }; 

¿Qué se necesita para entender que esta clase no se puede copiar? Nada más que entender lo que significa la syntax = delete . Cualquier libro que explique las reglas de syntax de C ++ 11 te dirá exactamente qué hace. El efecto de este código es obvio para el usuario inexperto de C ++.

Lo bueno de este modismo es que se convierte en un modismo porque es la manera más clara y más obvia de decir exactamente lo que quieres decir.

Incluso boost::noncopyable requiere un poco más de reflexión. Sí, se llama “no copiable”, por lo que es autodocumentado. Pero si nunca lo has visto antes, genera preguntas. ¿Por qué se deriva de algo que no se puede copiar? ¿Por qué mis mensajes de error hablan del constructor de copias de boost::noncopyable ? Etc. Nuevamente, entender el idioma requiere más esfuerzo mental.

Lo primero es que, como otros señalaron antes que yo, te equivocaste de idioma, declaraste como privado y no defines :

 class noncopyable { noncopyable( noncopyable const & ); noncopyable& operator=( noncopyable const & ); }; 

Donde el tipo de retorno del operator= puede ser básicamente cualquier cosa. En este punto, si lees ese código en un encabezado, ¿qué significa realmente? Solo puede ser copiado por amigos o no se puede copiar nunca? Tenga en cuenta que si proporciona una definición como en su ejemplo, indica que solo puedo copiarla dentro de la clase y por amigos , es la falta de definición lo que traduce esto en No puedo copiar . Pero la falta de una definición en el encabezado no es sinónimo de falta de definición en todas partes , ya que podría definirse en el archivo cpp.

Aquí es donde heredar de un tipo llamado noncopyable hace explícito que la intención es evitar copias, de la misma manera que si escribió manualmente el código anterior, debe documentar con un comentario en la línea que la intención es deshabilitar copias.

C ++ 11 no cambia nada de esto, solo hace que la documentación sea explícita en el código. En la misma línea que declara el constructor de copia como eliminado, se documenta que desea que esté completamente deshabilitado .

Como último comentario, la función en C ++ 11 no se trata solo de poder escribir no copiable con menos código o mejor, sino más bien de nhibernate que el comstackdor genere código que no desea que se genere. Este es solo un uso de esa característica.

Además de los puntos que otros han mencionado …

Tener un constructor de copias privadas y un operador de asignación de copias que no defina evita que alguien haga copias. Sin embargo, si una función miembro o una función amiga intenta hacer una copia, recibirán un error de tiempo de enlace. Si intentan hacerlo cuando haya eliminado explícitamente esas funciones, recibirán un error en tiempo de comstackción.

Siempre hago que mis errores ocurran lo antes posible. Haga que los errores en tiempo de ejecución ocurran en el punto de error en lugar de hacerlo más adelante (por lo tanto, haga que el error ocurra cuando cambie la variable en lugar de cuando la lea). Convierta todos los errores de tiempo de ejecución en errores de tiempo de enlace para que el código nunca tenga la posibilidad de ser incorrecto. Convierta todos los errores de tiempo de enlace en errores de tiempo de comstackción para acelerar el desarrollo y tener mensajes de error ligeramente más útiles.

Es más legible y permite al comstackdor dar mejores errores.

El ‘eliminado’ es más claro para un lector, especialmente si están pasando por alto la clase. Del mismo modo, el comstackdor puede decirle que está tratando de copiar un tipo que no se puede copiar, en lugar de darle un error genérico de “intentar acceder a un miembro privado”.

Pero en realidad, es solo una característica de conveniencia.

La gente de aquí está recomendando que declares funciones miembro sin definirlas. Me gustaría señalar que ese enfoque no es portátil. Algunos comstackdores / vinculadores requieren que si declara una función miembro, también debe definirla, incluso si no se utiliza. Si solo está utilizando VC ++, GCC, clang, entonces puede salirse con la suya, pero si está tratando de escribir un código realmente portátil, algunos otros comstackdores (por ejemplo, Green Hills) fallarán.