Std:: unique_ptr с неполным типом не будет компилироваться
Я использую pimpl-idiom с 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
должен быть использован с неполным типом. Является ли это ошибкой в libС++ или я делаю что-то неправильно здесь?
Ответы
Ответ 1
Вот несколько примеров 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 an empty body) 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_;
Ответ 2
Как упоминалось Alexandre C., проблема сводится к 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;
}
Ответ 3
Возможно, у вас есть некоторые тела функций внутри файла .h в классе, который использует неполный тип.
Убедитесь, что внутри окна .h для класса вы имеете только объявление функции. Все тела функций для окна должны находиться в файле .cpp. И для window_impl, а также...
Btw, вы должны явно добавить объявление деструктора для класса Windows в ваш .h файл.
Но вы НЕ МОЖЕТЕ положить пустой файл dtor в заголовочный файл:
class window {
virtual ~window() {};
}
Должно быть просто объявление:
class window {
virtual ~window();
}
Ответ 4
использовать пользовательский удаленный
Проблема заключается в том, что unique_ptr<T>
должен вызывать деструктор T::~T()
в свой собственный деструктор, его оператор назначения перемещения и unique_ptr::reset()
функция-член (только). Однако они должны быть вызваны (неявно или явно) в нескольких ситуациях PIMPL (уже во внешнем классе деструктора и оператора переадресации).
Как уже указывалось в другом ответе, один из способов избежать этого - переместить все операции, требующие unique_ptr::~unique_ptr()
, unique_ptr::operator=(unique_ptr&&)
и unique_ptr::reset()
в исходный файл, где фактически определен класс помощника pimpl.
Однако это довольно неудобно и в какой-то степени бросает вызов самой точке pimpl idoim. Это гораздо более чистое решение, которое позволяет избежать всего, что связано с использованием пользовательского делетера, и только переместить его определение в исходный файл, в котором живет помощник-помощник прыща. Вот простой пример:
// 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; }
Вместо отдельного класса делетера вы также можете использовать свободную функцию или static
члена foo
в сочетании с лямбдой:
class foo {
struct pimpl;
static void delete_pimpl(pimpl*);
std::unique_ptr<pimpl,[](pimpl*p){delete_pimpl(p);}> _pimpl;
};