2012-03-31 2 views
114

Я использую Pimpl-идиому с std::unique_ptr:станд :: 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>:

Invalid применение 'sizeof' к неполному типу '' uixx::window::window_impl

Насколько мне известно, std::unique_ptr должен использоваться с неполным типом. Это ошибка в libC++ или я делаю что-то не так?

+0

Сообщение полный код пожалуйста. Особенно конструкторы шаблонов. –

+0

@daknok_t: библиотека интеллектуальных указателей boost запрещает 'boost :: unique_ptr' работать с неполными типами: вместо этого вам пришлось использовать' boost :: shared_ptr'. Вы уверены, что стандартная библиотека C++ не имеет такого же ограничения? – Hurkyl

+4

@Alexandre: код, который OP размещен *, является * полным кодом для этого варианта использования. Файл, состоящий только из '#include ', и эти две строки должны компилироваться ... при условии, что' unique_ptr' разрешено использовать с неполными типами. – Hurkyl

ответ

153

Вот несколько примеров из 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_; 
+3

Я нахожу ваше первое решение (добавление ** foo ** destructor) позволяет самому объявлению класса компилировать, но объявляет объект этого тип везде приводит к исходной ошибке («неверное применение« sizeof »...»). –

+0

@ user192737: Не могли бы вы привести полный пример, где можно воспроизвести ошибку (pastebin или идеон или что-то еще)? –

+0

Несомненно! Это всего лишь ваш первый пример с main(), в котором создается экземпляр класса: http://pastebin.com/65jMYzsi Впоследствии я обнаружил, что добавление конструктора по умолчанию к ** foo ** заставляет ошибку уйти - Не знаю, почему. –

28

Как Alexandre C. упоминалось, проблема сводится к window «s деструктора неявным определяется в тех местах, где тип 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; 
} 
+1

Это правильное решение, насколько я могу судить. Это не единственно для использования pimpl-идиомы, это общая проблема с использованием std :: unique_ptr с неполными классами. По умолчанию deleter, используемый std :: unique_ptr , пытается выполнить «удалить X», чего не может сделать, если X является объявлением вперед. Указав функцию делетера, вы можете поместить эту функцию в исходный файл, где класс X полностью определен. Другие исходные файлы могут затем использовать std :: unique_ptr , хотя X является просто объявлением вперед, если они связаны с исходным файлом, содержащим DeleterFunc. – sheltond

+0

Это хорошее обходное решение, когда вы должны иметь встроенное определение функции, создающее экземпляр вашего типа «Foo» (например, статический метод getInstance, который ссылается на конструктор и деструктор), и вы не хотите переместить их в реализацию файл как @ adspx5. – GameSalutes

8

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

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

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

Но вы не можете положить пустое тело dtor в вас заголовок файла:

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

Должно быть просто декларация:

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

Это то, что я искал, спасибо. – David

7

использовать пользовательские Deleter

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

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

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

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

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

Он предоставляет общие строительные леса для этого шаблона: пользовательский класс делетера, который вызывает внешнюю функцию делетера, псевдоним типа для unique_ptr с этим классом deleter и макрос, чтобы объявить функцию делетера в ТУ, которая имеет полное определение типа. Я думаю, что это имеет некоторую общую полезность, поэтому вот оно:

#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 
Смежные вопросы