std :: unique_ptr с неполным типом не будет компилироваться - PullRequest
166 голосов
/ 31 марта 2012

Я использую 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 должен быть в состоянии использоваться снеполный тип.Это ошибка в libc ++ или я что-то здесь не так делаю?

Ответы [ 6 ]

219 голосов
/ 31 марта 2012

Вот несколько примеров 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_;
39 голосов
/ 30 июля 2013

Как упоминалось 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;
}
13 голосов
/ 28 августа 2015

использовать пользовательское средство удаления

Проблема в том, что unique_ptr<T> должен вызывать деструктор T::~T() в своем собственном деструкторе, своем операторе назначения перемещения и unique_ptr::reset() функции-члене (только). Однако они должны вызываться (неявно или явно) в нескольких ситуациях PIMPL (уже в деструкторе внешнего класса и в операторе присваивания перемещения).

Как уже указывалось в другом ответе, один из способов избежать этого - переместить все операции, требующие unique_ptr::~unique_ptr(), unique_ptr::operator=(unique_ptr&&) и unique_ptr::reset(), в исходный файл, где находится вспомогательный класс pimpl. на самом деле определяется.

Однако это довольно неудобно и в некоторой степени противоречит самой сути сутенерства. Гораздо более чистое решение, позволяющее избежать всего, что заключается в использовании пользовательского удалителя и перемещении его определения только в исходный файл, в котором находится вспомогательный класс pimple. Вот простой пример:

// file.h
class foo
{
  struct pimpl;
  struct pimpl_deleter { void operator()(pimpl*) const; };
  std::unique_ptr<pimpl,pimpl_deleter> m_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*ptr){delete_pimpl(ptr);}> m_pimpl;
};
13 голосов
/ 12 апреля 2014

Возможно, у вас есть несколько тел функций в файле .h внутри класса, который использует неполный тип.

Убедитесь, что в вашем .h для окна класса у вас есть только объявление функции. Все тела функций для окна должны быть в файле .cpp. И для window_impl ...

Кстати, вы должны явно добавить объявление деструктора для класса Windows в вашем .h файле.

Но вы НЕ МОЖЕТЕ поместить пустое тело dtor в заголовочный файл:

class window {
    virtual ~window() {};
  }

Должно быть просто объявление:

  class window {
    virtual ~window();
  }
1 голос
/ 30 ноября 2018

Может быть не лучшим решением, но иногда вы можете использовать shared_ptr .Если, конечно, это немного излишне, но ... что касается unique_ptr, я подожду еще 10 лет, пока разработчики стандартов C ++ не решат использовать лямбду в качестве удалителя.

Другая сторона.По вашему коду может случиться так, что на этапе уничтожения window_impl будет неполным.Это может быть причиной неопределенного поведения.Смотрите это: Почему, действительно, удаление неполного типа является неопределенным поведением?

Итак, если возможно, я бы определил очень базовый объект для всех ваших объектов с помощью виртуального деструктора.И ты почти хорош.Вы просто должны помнить, что система будет вызывать виртуальный деструктор для вашего указателя, поэтому вы должны определить его для каждого предка.Вы также должны определить базовый класс в разделе наследования как виртуальный (подробности см. this ).

1 голос
/ 13 февраля 2018

Чтобы добавить к ответам других о пользовательском удалителе, в нашей внутренней "библиотеке утилит" я добавил вспомогательный заголовок для реализации этого общего шаблона (std::unique_ptr неполного типа, известного только некоторым из TU, например, чтобы избежать длительное время компиляции или предоставление просто непрозрачного дескриптора клиентам).

Он предоставляет общий каркас для этого шаблона: пользовательский класс для удаления, который вызывает внешнюю функцию удаления, псевдоним типа для unique_ptr с этим классом для удаления и макрос для объявления функции удаления в 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
...