2012-01-26 16 views
54

Oto uproszczenie tego, co widzę, gdy próbuję użyć unique_ptr dla pimpl. Wybrałem unique_ptr, ponieważ naprawdę chcę, aby klasa posiadała wskaźnik - chcę, aby okresy życia wskaźnika pimpl i klasy były takie same.Jak korzystać z unique_ptr dla pimpl?

W każdym razie, oto nagłówek:

#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 

Oto źródło:

#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}} 
{ } 

mogę skompilować je w bibliotece dobrze. Ale gdy próbuję go użyć w programie testowym uzyskać

[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' 

Jest to dobrze znany safety feature. Próbowałem podążać.

Mój problem polega na tym, że jeśli wstawię nagłówek Pomoc :: Impl w nagłówku, wydaje się, że można znieść wszelkie zalety pimpl. Układ klas jest widoczny dla użytkowników. Definicja jest ukryta, ale mogłem to zrobić z klasą pomocy i członkami prywatnymi. Ponadto, w tym deklaracja Impl wprowadza nowe nagłówki, które chciałbym zachować osobno.

Czego mi brakuje? Co ludzie umieszczają w deklaracji Impl i gdzie? Czy robię Dtor Pomoc źle? Argh!

+2

Zobacz także [GotW # 101: firewalle kompilacyjne, część 2] (http://herbsutter.com/gotw/_101/) i [to powiązane pytanie] (http://stackoverflow.com/q/8595471/636019). – ildjarn

+1

Mimo że jest to stare pytanie, prawdopodobnie warto podkreślić, że [jak wyjaśniono w cppreference] (http://en.cppreference.com/w/cpp/language/pimpl), PImpl zaimplementowany z 'unique_ptr' powinien być opakowane w coś takiego jak ['propagate_const'] (http://en.cppreference.com/w/cpp/experimental/propagate_const) dla pełnej poprawności. – jdehesa

+1

@jdehesa Dziękuję, patrzyłem na propagate_const jako rozwiązanie pewnej nieporęczności interfejsu API. Prawie się zastanawiam, czy unique_ptr jest domyślnie zepsute w tym sensie. Wygląda na to, że propagate_const powinno być wbudowane lub przynajmniej być domyślnym. – emsr

Odpowiedz

69

Wierzę, że Twój test_help.cpp rzeczywiście widzi destruktor ~Help(), który został zadeklarowany jako domyślny. W tym destruktorze kompilator próbuje również wygenerować destruktor unique_ptr, ale potrzebuje do tego deklaracji .

Jeśli przeniesiesz definicję destruktora do Help.cpp, problem ten powinien zniknąć.

- EDIT - Można określić destruktor być domyślną w pliku cpp też:

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

OK, w nagłówku po prostu * zadeklarowałem * dtor za pomoc. Następnie w pliku implementacji umieszczam Help :: ~ Help() = default; Woot !!! Dzięki. Wszystko działa poprawnie! – emsr

+0

Doskonałe rzeczy –

+4

Inteligentne wskaźniki, w tym 'unique_ptr', są wymagane do pracy z niekompletnym typem. Funkcja "deletera" jest przypisywana w czasie budowy, więc użytkownicy wskaźnika nie muszą wiedzieć, w jaki sposób został usunięty. Jeśli to rozwiąże problem, oznacza to, że jego implementacja 'unique_ptr' jest zepsuta. –

1

Uwaga ta z definicji: unique_ptr

std :: unique_ptr może być skonstruowany dla niekompletnego typu T, tak aby ułatwić użycie jako uchwytu w idiomu pImpl. Jeśli używany jest domyślny deleter, T musi być kompletny w punkcie w kodzie, w którym wywoływacz jest wywoływany, co dzieje się w destruktorze, przenieść operator przypisania i zresetować funkcję składową std :: unique_ptr.