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_;
As Александр С. упомянуто, проблема сводится к 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. Гораздо более чистое решение, которое позволяет избежать всего, что использовать пользовательским 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