c++ unique-ptr incomplete-type libc++

c++ - std:: unique_ptr con un tipo incompleto no compilará



unique-ptr incomplete-type (5)

Estoy usando el pimpl-idioma 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, aparece un error de compilación con respecto al uso de un tipo incompleto, en la línea 304 en <memory> :

Aplicación invá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í?


Aquí hay algunos ejemplos de std::unique_ptr con tipos incompletos. El problema radica en la destrucción.

Si usa pimpl con unique_ptr , necesita declarar un destructor:

class foo { class impl; std::unique_ptr<impl> impl_; public: foo(); // You may need a def. constructor to be defined elsewhere ~foo(); // Implement (with an empty body) where impl is complete };

porque de lo contrario el compilador genera uno predeterminado, y necesita una declaración completa de foo::impl para esto.

Si tienes constructores de plantilla, entonces estás jodido, incluso si no construyes el miembro impl_ :

template <typename T> foo::foo(T bar) { // Here the compiler needs to know how to // destroy impl_ in case an exception is // thrown ! }

En el ámbito del espacio de nombres, el uso de unique_ptr tampoco funcionará:

class impl; std::unique_ptr<impl> impl_;

ya que el compilador debe saber aquí cómo destruir este objeto de duración estática. Una solución alternativa es:

class impl; struct ptr_impl : std::unique_ptr<impl> { ~ptr_impl(); // Implement (empty body) elsewhere } impl_;


Como lo mencionó Alexandre C. , el problema se reduce al destructor de la window que se define implícitamente en lugares donde el tipo de window_impl aún está incompleto. Además de sus soluciones, otra solución que he utilizado es declarar un functor Deleter en el encabezado:

// Foo.h class FooImpl; struct FooImplDeleter { void operator()(FooImpl *p); } class Foo { ... private: std::unique_ptr<FooImpl, FooImplDeleter> impl_; }; // Foo.cpp ... void FooImplDeleter::operator()(FooImpl *p) { delete p; }


Para agregar a las respuestas de los demás 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 un tipo incompleto, conocido solo para algunas de las UT, por ejemplo, para evitar una compilación larga) veces o para proporcionar solo un asa opaca a 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 un unique_ptr con esta clase de modificación y una macro para declarar la función de eliminación en una TU que tiene una definición completa de el tipo. Creo que esto tiene cierta utilidad general, así que aquí está:

#ifndef CZU_UNIQUE_OPAQUE_HPP #define CZU_UNIQUE_OPAQUE_HPP #include <memory> /** Helper to define a `std::unique_ptr` that works just with a forward declaration The "regular" `std::unique_ptr<T>` requires the full definition of `T` to be available, as it has to emit calls to `delete` in every TU that may use it. A workaround to this problem is to have a `std::unique_ptr` with a custom deleter, which is defined in a TU that knows the full definition of `T`. This header standardizes and generalizes this trick. The usage is quite simple: - everywhere you would have used `std::unique_ptr<T>`, use `czu::unique_opaque<T>`; it will work just fine with `T` being a forward declaration; - in a TU that knows the full definition of `T`, at top level invoke the macro `CZU_DEFINE_OPAQUE_DELETER`; it will define the custom deleter used by `czu::unique_opaque<T>` */ namespace czu { template<typename T> struct opaque_deleter { void operator()(T *it) { void opaque_deleter_hook(T *); opaque_deleter_hook(it); } }; template<typename T> using unique_opaque = std::unique_ptr<T, opaque_deleter<T>>; } /// Call at top level in a C++ file to enable type %T to be used in an %unique_opaque<T> #define CZU_DEFINE_OPAQUE_DELETER(T) namespace czu { void opaque_deleter_hook(T *it) { delete it; } } #endif


Probablemente tengas algunos cuerpos de funciones dentro del archivo .h dentro de la clase que usa el tipo incompleto.

Asegúrese de que en su ventana .h for class solo tenga 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, tienes que agregar explícitamente la declaración de destructor para la clase de Windows en tu archivo .h.

Pero NO PUEDES poner el cuerpo dtor vacío en tu archivo de encabezado:

class window { virtual ~window() {}; }

Debe ser solo una declaración:

class window { virtual ~window(); }


usa un eliminador personalizado

El problema es que unique_ptr<T> debe llamar al destructor T::~T() en su propio destructor, su operador de asignación de movimiento y la función de miembro unique_ptr::reset() (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 señaló en otra respuesta, una forma de evitar eso es mover todas las operaciones que requieren unique_ptr::~unique_ptr() , unique_ptr::operator=(unique_ptr&&) , y unique_ptr::reset() en el archivo de origen donde el la clase de ayudante pimpl está realmente definida.

Sin embargo, esto es bastante inconveniente y desafía en cierta medida el punto mismo de los pimpl. Una solución mucho más limpia que evita todo lo que es usar un eliminador personalizado y solo mover su definición al archivo fuente donde vive la clase de ayudante de espinillas. Aquí hay un ejemplo simple:

// file.h class foo { struct pimpl; struct pimpl_deleter { void operator()(pimpl*) const; }; std::unique_ptr<pimpl,pimpl_deleter> _pimpl; public: foo(some data); foo(foo&&) = default; // no need to define this in file.cc foo&operator=(foo&&) = default; // no need to define this in file.cc //foo::~foo() auto-generated: no need to define this in file.cc }; // file.cc struct foo::pimpl { // lots of complicated code }; void foo::pimpl_deleter::operator()(foo::pimpl*ptr) const { delete ptr; }

En lugar de una clase de eliminación independiente, también puede usar una función gratuita o un miembro static de foo junto con una lambda:

class foo { struct pimpl; static void delete_pimpl(pimpl*); std::unique_ptr<pimpl,[](pimpl*p){delete_pimpl(p);}> _pimpl; };