Я использую 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 ++ или я что-то здесь не так делаю?
Ответы:
Вот несколько примеров
std::unique_ptr
с неполными типами. Проблема заключается в разрушении.Если вы используете pimpl с
unique_ptr
, вам нужно объявить деструктор:потому что в противном случае компилятор генерирует один по умолчанию, и ему нужно полное объявление
foo::impl
.Если у вас есть конструкторы шаблонов, то вы облажались, даже если вы не создаете
impl_
элемент:В области пространства имен использование
unique_ptr
также не будет работать:поскольку компилятор должен знать здесь, как уничтожить этот статический объект продолжительности. Обходной путь:
источник
foo::~foo() = default;
в файл srcКак отметил Александр С. , проблема сводится к тому,
window
что деструктор неявно определяется в местах, где типwindow_impl
все еще не завершен. В дополнение к его решениям я использовал еще один обходной путь - объявить функтор Deleter в заголовке:Обратите внимание, что использование пользовательской функции Deleter исключает использование
std::make_unique
(доступно из C ++ 14), как уже обсуждалось здесь .источник
использовать пользовательский удалитель
Проблема заключается в том,
unique_ptr<T>
что деструктор должен вызыватьT::~T()
свой собственный деструктор, свой оператор присваивания перемещения иunique_ptr::reset()
функцию-член (только). Однако они должны вызываться (неявно или явно) в нескольких ситуациях PIMPL (уже в деструкторе внешнего класса и операторе присваивания перемещения).Как уже указывалось в другой ответ, один из способов избежать этого, чтобы переместить все операции, требующие
unique_ptr::~unique_ptr()
,unique_ptr::operator=(unique_ptr&&)
иunique_ptr::reset()
в исходный файл , в котором фактически определен класс Pimpl помощник.Тем не менее, это довольно неудобно и в некоторой степени противоречит самой сути проблемы. Гораздо более чистое решение, позволяющее избежать всего, что заключается в использовании пользовательского средства удаления и только перемещать его определение в исходный файл, где живет класс помощника pimple . Вот простой пример:
Вместо отдельного класса для удаления вы также можете использовать свободную функцию или
static
членfoo
в сочетании с лямбда-выражением:источник
Возможно, у вас есть некоторые тела функций в файле .h внутри класса, который использует неполный тип.
Убедитесь, что в вашем .h для окна класса у вас есть только объявление функции. Все тела функций для окна должны быть в файле .cpp. И для window_impl также ...
Кстати, вы должны явно добавить объявление деструктора для класса Windows в вашем .h файле.
Но вы НЕ МОЖЕТЕ поместить пустое тело dtor в заголовочный файл:
Должно быть просто декларация:
источник
Чтобы добавить ответы других о пользовательском удалителе, в нашей внутренней «библиотеке утилит» я добавил вспомогательный заголовок для реализации этого общего шаблона (
std::unique_ptr
неполного типа, известного только некоторым TU, например, чтобы избежать длительного времени компиляции или обеспечить просто непрозрачная ручка для клиентов).Он предоставляет общие условия для этого шаблона: пользовательский класс средства удаления, который вызывает внешнюю определенную функцию средства удаления, псевдоним типа для
unique_ptr
класса с этим классом средства удаления и макрос для объявления функции средства удаления в TU, который имеет полное определение тип. Я думаю, что это имеет некоторую общую полезность, поэтому вот оно:источник
Может быть не лучшим решением, но иногда вы можете использовать shared_ptr . Если, конечно, это немного излишне, но ... что касается unique_ptr, я подожду еще 10 лет, пока разработчики стандартов C ++ не решат использовать лямбду в качестве средства удаления.
Другая сторона. По вашему коду может случиться так, что на этапе уничтожения window_impl будет неполным. Это может быть причиной неопределенного поведения. Посмотрите на это: почему действительно удаление неполного типа является неопределенным поведением?
Поэтому, если возможно, я бы определил очень базовый объект для всех ваших объектов с помощью виртуального деструктора. И ты почти хорош. Вы просто должны помнить, что система будет вызывать виртуальный деструктор для вашего указателя, поэтому вы должны определить его для каждого предка. Вам также следует определить базовый класс в разделе наследования как виртуальный ( подробности см. В этом разделе ).
источник