std:: уникальный ptr с неполным типом не компилируется
Я использую pimpl-идиому с std::unique_ptr
:
class window {
window(const rectangle& rect);
private:
class window_impl; // defined elsewhere
std::unique_ptr<window_impl> impl_; // won't compile
};
однако, я получаю ошибку компиляции относительно использования неполного типа, на строке 304 в <memory>
:
недопустимое применение '
sizeof
'к неполному типу'uixx::window::window_impl
'
насколько я знаю,std::unique_ptr
должен быть в состоянии использоваться с неполным типом. Это баг в libc++ или я делаю что-то неправильно здесь?
5 ответов:
вот несколько примеров
std::unique_ptr
с неполным типом. Проблема заключается в разрушении.если вы используете pimpl с
unique_ptr
, вам нужно объявить деструктор:class foo { class impl; std::unique_ptr<impl> impl_; public: foo(); // You may need a def. constructor to be defined elsewhere ~foo(); // Implement (with {}, or with = default;) where impl is complete };
потому что в противном случае компилятор генерирует значение по умолчанию, и ему требуется полное объявление
foo::impl
для этого.если у вас есть шаблоны конструкторов, то вы облажались, даже если вы не строите
impl_
член:template <typename T> foo::foo(T bar) { // Here the compiler needs to know how to // destroy impl_ in case an exception is // thrown ! }
в области видимости пространства имен , используя
unique_ptr
не работает:class impl; std::unique_ptr<impl> impl_;
так как компилятор должен знать, как уничтожить этот статический объект продолжительность. Обходной путь:
class impl; struct ptr_impl : std::unique_ptr<impl> { ~ptr_impl(); // Implement (empty body) elsewhere } impl_;
как Александр С. упоминалось, проблема сводится к
window
деструктор неявно определяется в местах, где типwindow_impl
все еще не завершена. В дополнение к его решениям, другим обходным путем, который я использовал, является объявление функтора Deleter в заголовке:// 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; }
вероятно, у вас есть некоторые тела функции .H-файл класса, который использует неполный тип.
убедитесь, что в вашей .h для окна класса у вас есть только объявление функции. Все тела функций для window должны быть включены .файл cpp. И для window_impl, а также...
Кстати ,вы должны явно добавить объявление деструктора для класса windows в свой.H-файл.
но вы не можете поместить пустое тело dtor в заголовочный файл:
class window { virtual ~window() {}; }
должны будьте просто объявлением:
class window { virtual ~window(); }
использовать пользовательским deleter
проблема в том, что
unique_ptr<T>
должен вызвать деструкторT::~T()
в своем собственном деструкторе, его оператор присваивания перемещения иunique_ptr::reset()
функции-члена (только). Однако они должны вызываться (неявно или явно) в нескольких ситуациях PIMPL (уже в деструкторе внешнего класса и операторе присваивания перемещения).Как уже указывалось в другом ответе, один из способов избежать этого-двигаться все операции, которые требуют
unique_ptr::~unique_ptr()
,unique_ptr::operator=(unique_ptr&&)
иunique_ptr::reset()
в исходный файл, где фактически определен вспомогательный класс pimpl.тем не менее, это довольно неудобно и в какой-то степени бросает вызов самой точке pimpl idoim. Гораздо более чистое решение, которое позволяет избежать всего, что нужно использовать custom deleter и только переместите его определение в исходный файл, где живет вспомогательный класс pimple. Вот простой пример:
// 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; }
вместо отдельного класс deleter, вы также можете использовать свободную функцию или
static
членfoo
в сочетании с лямбда:class foo { struct pimpl; static void delete_pimpl(pimpl*); std::unique_ptr<pimpl,[](pimpl*p){delete_pimpl(p);}> _pimpl; };
чтобы добавить к ответам другого о пользовательском удалителе, в нашей внутренней "библиотеке утилит" я добавил вспомогательный заголовок для реализации этого общего шаблона (
std::unique_ptr
неполного типа, известного только некоторым из TU, чтобы, например, избежать длительного времени компиляции или предоставить клиентам только непрозрачный дескриптор).Он предоставляет общие леса для этого шаблона: пользовательский класс deleter, который вызывает внешне определенную функцию deleter, псевдоним типа для
unique_ptr
С этим deleter класс и макрос для объявления функции удаления в TU, которая имеет полное определение типа. Я думаю, что это имеет некоторую общую полезность, так что вот она:#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