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