2012-01-26 6 views
54

Вот упрощение того, что я вижу, когда пытаюсь использовать unique_ptr для pimpl. Я выбрал unique_ptr, потому что я действительно хочу, чтобы класс владел указателем - я хочу, чтобы сроки жизни указателя pimpl и класса были одинаковыми.Как использовать unique_ptr для pimpl?

Во всяком случае, вот заголовок:

#ifndef HELP 
#define HELP 1 

#include <memory> 

class Help 
{ 

public: 

    Help(int ii); 
    ~Help() = default; 

private: 

    class Impl; 
    std::unique_ptr<Impl> _M_impl; 
}; 

#endif // HELP 

Вот источник:

#include "Help.h" 

class Help::Impl 
{ 
public: 
    Impl(int ii) 
    : _M_i{ii} 
    { } 

private: 

    int _M_i; 
}; 

Help::Help(int ii) 
: _M_impl{new Help::Impl{ii}} 
{ } 

я мог собрать их в библиотеку просто отлично. Но когда я пытаюсь использовать его в тестовой программе я

[email protected]:~/ext_distribution$ ../bin/bin/g++ -std=c++0x -o test_help test_help.cpp Help.cpp 
In file included from /home/ed/bin/lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/memory:86:0, 
       from Help.h:4, 
       from test_help.cpp:3: 
/home/ed/bin/lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/bits/unique_ptr.h: In instantiation of 'void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Help::Impl]': 
/home/ed/bin/lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/bits/unique_ptr.h:245:4: required from 'void std::unique_ptr<_Tp, _Dp>::reset(std::unique_ptr<_Tp, _Dp>::pointer) [with _Tp = Help::Impl; _Dp = std::default_delete<Help::Impl>; std::unique_ptr<_Tp, _Dp>::pointer = Help::Impl*]' 
/home/ed/bin/lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/bits/unique_ptr.h:169:32: required from 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Help::Impl; _Dp = std::default_delete<Help::Impl>]' 
Help.h:6:7: required from here 
/home/ed/bin/lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/bits/unique_ptr.h:63:14: error: invalid application of 'sizeof' to incomplete type 'Help::Impl' 

Это хорошо известный safety feature. Я пытался следовать.

Моя проблема заключается в том, что если бы я ввел объявление Help :: Impl в заголовок, это, казалось бы, устранило бы любое преимущество pimpl. Макет класса отображается пользователям. Определение скрыто, но я мог бы сделать это с помощью класса справки и частных членов. Кроме того, в том числе декларация Impl содержит новые заголовки, которые я хотел бы сохранить отдельно.

Что мне не хватает? Что люди ставят в декларации Impl и где? Я неправильно делаю помощь? Argh!

+2

См. Также [GotW # 101: Компиляция межсетевых экранов, часть 2] (http://herbsutter.com/gotw/_101/) и [этот связанный вопрос] (http://stackoverflow.com/q/8595471/636019). – ildjarn

+1

Даже если это старый вопрос, вероятно, важно отметить, что [как объяснено в cppreference] (http://en.cppreference.com/w/cpp/language/pimpl), PImpl, реализованный с 'unique_ptr', должен быть обернутым чем-то вроде ['propagate_const'] (http://en.cppreference.com/w/cpp/experimental/propagate_const) для полной корректности. – jdehesa

+1

@jdehesa Спасибо, я смотрел на propagate_const как на решение какой-то неловкости API. Я почти задаюсь вопросом, по умолчанию ли unique_ptr по умолчанию в этом смысле. Похоже, что propagate_const должен быть встроен или, по крайней мере, по умолчанию. – emsr

ответ

69

Я считаю, что ваш test_help.cpp на самом деле видит деструктор ~Help(), который вы указали по умолчанию. В этом деструкторе компилятор также пытается сгенерировать дескриптор unique_ptr, но для этого ему требуется объявление Impl.

Итак, если вы переместите определение деструктора в Help.cpp, эта проблема должна исчезнуть.

- EDIT - Вы можете определить деструктор быть по умолчанию в файле CPP, тоже:

Help::~Help() = default; 
+9

Хорошо, в заголовке я просто * объявил * dtor для справки. Затем в файле реализации я устанавливаю Help :: ~ Help() = default; Woot !!! Благодарю. Все работает! – emsr

+0

Отличный материал –

+4

Умные указатели, в том числе 'unique_ptr', должны работать с неполным типом. Функция «deleter» назначается во время построения, так что пользователям указателя не нужно знать, как они удаляются. Если это устраняет проблему, это означает, что реализация 'unique_ptr' нарушена. –

1

Примечание это из unique_ptr определения:

станд :: unique_ptr могут быть построены для неполного типа T, например, чтобы облегчить использование в качестве ручки в идиоме pImpl. Если используется дефолт по умолчанию, T должен быть завершен в точке кода, в которой запущен делектор, что происходит в деструкторе, операции переноса задания и сбросить функцию-член std :: unique_ptr.