Estoy usando el pimpl-idiom con std::unique_ptr
:
class window {
window(const rectangle& rect);
private:
class window_impl; // defined elsewhere
std::unique_ptr<window_impl> impl_; // won't compile
};
Sin embargo, recibo un error de compilación con respecto al uso de un tipo incompleto, en la línea 304 en <memory>
:
Aplicación no válida de '
sizeof
' a un tipo incompleto 'uixx::window::window_impl
'
Por lo que sé, std::unique_ptr
debería poder usarse con un tipo incompleto. ¿Es esto un error en libc ++ o estoy haciendo algo mal aquí?
Respuestas:
Aquí hay algunos ejemplos de
std::unique_ptr
tipos incompletos. El problema radica en la destrucción.Si usa pimpl con
unique_ptr
, debe declarar un destructor:porque de lo contrario, el compilador genera uno predeterminado y necesita una declaración completa de
foo::impl
para esto.Si tienes constructores de plantillas, entonces estás jodido, incluso si no construyes el
impl_
miembro:En el ámbito del espacio de nombres, el uso
unique_ptr
tampoco funcionará:ya que el compilador debe saber aquí cómo destruir este objeto de duración estática. Una solución alternativa es:
fuente
foo::~foo() = default;
en el archivo srcComo mencionó Alexandre C. , el problema se reduce a que
window
el destructor se define implícitamente en lugares donde el tipo dewindow_impl
todavía está incompleto. Además de sus soluciones, otra solución alternativa que he usado es declarar un functor Deleter en el encabezado:Tenga en cuenta que el uso de una función Deleter personalizada impide el uso de
std::make_unique
(disponible en C ++ 14), como ya se discutió aquí .fuente
usar un eliminador personalizado
El problema es que
unique_ptr<T>
debe llamar al destructorT::~T()
en su propio destructor, su operador de asignación de movimiento y launique_ptr::reset()
función miembro (solo). Sin embargo, estos deben llamarse (implícita o explícitamente) en varias situaciones PIMPL (ya en el destructor de la clase externa y el operador de asignación de movimiento).Como ya se ha señalado en otra respuesta, una forma de evitar que se va a mover todas las operaciones que requieren
unique_ptr::~unique_ptr()
,unique_ptr::operator=(unique_ptr&&)
yunique_ptr::reset()
en el archivo de origen, donde la clase pimpl ayudante se define realmente.Sin embargo, esto es bastante inconveniente y desafía hasta cierto punto el punto mismo de la idolatría. Una solución mucho más limpia que evita todo eso es usar un eliminador personalizado y solo mover su definición al archivo fuente donde vive la clase de ayuda de espinillas. Aquí hay un ejemplo simple:
En lugar de una clase de eliminación separada, también puede usar una función o
static
miembro librefoo
junto con un lambda:fuente
Probablemente tenga algunos cuerpos de función dentro del archivo .h dentro de la clase que usa un tipo incompleto.
Asegúrese de que dentro de su ventana .h para clase solo tenga una declaración de función. Todos los cuerpos de función para la ventana deben estar en el archivo .cpp. Y para window_impl también ...
Por cierto, debe agregar explícitamente la declaración de destructor para la clase de Windows en su archivo .h.
Pero NO PUEDE poner el cuerpo dtor vacío en su archivo de encabezado:
Debe ser solo una declaración:
fuente
Para agregar a las respuestas del otro sobre el eliminador personalizado, en nuestra "biblioteca de utilidades" interna agregué un encabezado auxiliar para implementar este patrón común (
std::unique_ptr
de tipo incompleto, conocido solo por algunos de los TU para, por ejemplo, evitar largos tiempos de compilación o para proporcionar solo un mango opaco para los clientes).Proporciona el andamiaje común para este patrón: una clase de eliminación personalizada que invoca una función de eliminación definida externamente, un alias de tipo para
unique_ptr
con esta clase de eliminación y una macro para declarar la función de eliminación en una TU que tiene una definición completa de tipo. Creo que esto tiene alguna utilidad general, así que aquí está:fuente
Puede que no sea la mejor solución, pero a veces puede usar shared_ptr en su lugar. Si por supuesto es un poco exagerado, pero ... en cuanto a unique_ptr, tal vez esperaré 10 años más hasta que los creadores de estándares de C ++ decidan usar lambda como deletor.
Otro lado. Según su código, puede suceder que en la etapa de destrucción window_impl esté incompleto. Esto podría ser una razón de comportamiento indefinido. Vea esto: ¿Por qué, realmente, eliminar un tipo incompleto es un comportamiento indefinido?
Entonces, si es posible, definiría un objeto muy básico para todos sus objetos, con destructor virtual. Y ya casi eres bueno. Debe tener en cuenta que el sistema llamará al destructor virtual para su puntero, por lo que debe definirlo para cada antepasado. También debe definir la clase base en la sección de herencia como virtual (consulte esto para más detalles).
fuente