c++ c++11 pimpl-idiom incomplete-type gotw

c++ - ¿La "solución" de GotW#101 realmente resuelve algo?



c++11 pimpl-idiom (2)

Primero lea las publicaciones de Herb''s Sutters GotW sobre pimpl en C ++ 11:

Tengo algunos problemas para entender la solución propuesta en GotW # 101. Por lo que puedo entender, todos los problemas resueltos laboriosamente en GotW # 100 han vuelto con fuerza:

  • Los miembros de pimpl son plantillas fuera de línea, y las definiciones no son visibles en el punto de uso (en la definición de class widget de clase y las funciones de miembro especiales de widget implícitamente generadas). Tampoco hay ninguna instanciación explícita. Esto causará errores externos no resueltos durante el enlace.

  • widget::impl todavía está incompleto en el punto donde se pimpl<widget::impl>::~pimpl() (no creo que en realidad se haya creado una instancia, solo se hace referencia). Así que std::unique_ptr<widget::impl>::~unique_ptr() llama a delete en un puntero a tipo incompleto, lo que produce un comportamiento indefinido si widget::impl tiene un destructor no trivial.

Explique qué obliga al compilador a generar miembros especiales en un contexto en el que widget::impl está completo. Porque no puedo ver cómo funciona esto.

Si GotW # 101 aún requiere una definición explícita de widget::~widget() en el archivo de implementación, donde widget::impl está completo, entonces explique el comentario "Más robusto" (que @sehe citó en su respuesta).

Estoy considerando la afirmación principal de GotW # 101 de que el envoltorio "elimina algunas piezas de repetitivo", lo que me parece (basado en el resto del párrafo) que significa la declaración y definición de widget::~widget() . Entonces, por favor, no confíe en eso en su respuesta, en GotW # 101, ¡eso se ha ido!

Herb, si pasas por aquí, hazme saber si sería correcto cortar + pegar el código de la solución aquí como referencia.


Creo que la confusión es la siguiente: el envoltorio pimpl puede ser una plantilla, la clase de widget no es:

demo.h

#include "pimpl_h.h" // in header file class widget { public: widget(); ~widget(); private: class impl; pimpl<impl> pimpl_; };

demo.cpp

#include "demo.h" #include "pimpl_impl.h" // in implementation file class widget::impl { // ::: }; widget::widget() : pimpl_() { } widget::~widget() { } // or =default

Como puede ver, nadie verá un constructor ''con plantilla'' para la clase de widget. Solo habrá una definición de él, y no habrá necesidad de una "instanciación explícita".

A la inversa, el ~pimpl<> destructor solo se ~pimpl<> una instancia desde el único punto de definición del ~widget() destructor. En ese punto, la clase impl está completa, por definición.

No hay errores de vinculación y no hay violaciones de ODR / UB.

Bonificación / beneficios adicionales

Como el propio Herb explica acertadamente en su publicación ( ¿Por qué es esto una mejora sobre el Pimpl Idiom enrollado a mano? 1 ), hay muchas más ventajas de usar este envoltorio pimpl, que se deriva de la reutilización de las tripas de implementación:

  • Utilice una plantilla para protegerse contra las fallas innecesarias.
  • obtiene los variadic, constructores de reenvío perfecto con C ++ 0x / C ++ 11, sin necesidad de soñar con una plantilla de constructor de plantilla con la lista de argumentos variadic rvalue-reffed que reenvía el paquete de argumentos al constructor de las clases envueltas perfectamente, etc. etc.

En resumen: SECO y conveniencia.

1 para citar (énfasis mío):

  • Primero, el código es más simple porque elimina algunas piezas de repetición : en la versión enrollada a mano, también tiene que declarar el constructor y escribir su cuerpo en el archivo de implementación y asignar explícitamente el objeto impl. También debe recordar declarar el destructor y escribir su cuerpo en el archivo de implementación, por razones de lenguaje oscuro explicadas en el # 100.
  • En segundo lugar, el código es más robusto : en la versión enrollada a mano, si olvida escribir el destructor fuera de línea, la clase Pimpl''d se compilará de forma aislada y parecerá que está en un estado de check-in, pero no podrá compilarse cuando lo use un llamante que intenta destruir un objeto y encuentra un útil "no puede generar un destructor porque impl es, uh, ya sabes, error incompleto" que deja al autor del código de la llamada que se rasca la cabeza mientras camina Acude a tu oficina para escabullirte por revisar algo roto.

Estás en lo correcto; El ejemplo parece faltar en una instanciación explícita de la plantilla. Cuando intento ejecutar el ejemplo con un constructor y un destructor para widget::impl en MSVC 2010 SP1, recibo un error de vinculador para pimpl<widget::impl>::pimpl() y pimpl<widget::impl>::~pimpl() . Cuando agrego la template class pimpl<widget::impl>; , enlaza bien.

En otras palabras, GotW # 101 elimina todas las repeticiones de GotW # 100, pero debe agregar una instanciación explícita de la pimpl<...> con la implementación de la implementación de pimpl . Al menos con # 101 la placa de caldera que necesita es sencilla.