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;
};