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