2011-02-07 32 views
5

Przy użyciu inteligentne kursory z idiomu pImpl, jak wPimpl inteligentnych wskaźników w klasie konstruktora szablonu: dziwne niepełnej typu kwestii

struct Foo 
{ 
private: 
    struct Impl; 
    boost::scoped_ptr<Impl> pImpl; 
}; 

oczywistym problemem jest to, że Foo::Impl jest niekompletna w punkcie, w którym destruktor Foo jest generowany.

Kompilatory zazwyczaj wysyłają tam ostrzeżenie, a boost::checked_delete, które jest używane wewnętrznie przez inteligentne wskaźniki Boost, statycznie potwierdza, że ​​klasa Foo::Impl jest kompletna i uruchamia błąd, jeśli tak nie jest.

W powyższym przykładzie do kompilacji, jeden dlatego musi napisać

struct Foo 
{ 
    ~Foo(); 

private: 
    struct Impl; 
    boost::scoped_ptr<Impl> pImpl; 
}; 

i wdrożenie pusty Foo::~Foo w pliku wdrażania, gdzie Foo::Impl jest kompletna. To jest przewaga inteligentnych wskaźników nad gołymi wskaźnikami, ponieważ nie możemy nie wdrożyć destruktora.

Jak dotąd, tak dobrze. Ale natknąłem się na dziwne zachowanie się przy próbie wprowadzenia konstruktora szablonu w podobny Bar klasy (pełny kod, spróbuj to sam):

// File Bar.h 
#ifndef BAR_H 
#define BAR_H 1 

#include <vector> 
#include <boost/scoped_ptr.hpp> 

struct Bar 
{ 
    template <typename I> 
    Bar(I begin, I end); 

    ~Bar(); 

private: 
    struct Impl; 
    boost::scoped_ptr<Impl> pImpl; 

    void buildImpl(std::vector<double>&); 
}; 


template <typename I> 
Bar::Bar(I begin, I end) 
{ 
    std::vector<double> tmp(begin, end); 
    this->buildImpl(tmp); 
} 

#endif // BAR_H 

// File Bar.cpp 
#include "Bar.h" 

struct Bar::Impl 
{ 
    std::vector<double> v; 
}; 

void Bar::buildImpl(std::vector<double>& v) 
{ 
    pImpl.reset(new Impl); 
    pImpl->v.swap(v); 
} 

Bar::~Bar() {} 

// File Foo.h 
#ifndef FOO_H 
#define FOO_H 1 

#include <boost/scoped_ptr.hpp> 


struct Foo 
{ 
    Foo(); 
    ~Foo(); 

private: 
    struct Impl; 
    boost::scoped_ptr<Impl> pImpl; 
}; 

#endif // FOO_H 

// File Foo.cpp 
#include "Foo.h" 

struct Foo::Impl 
{}; 


Foo::Foo() : pImpl(new Impl) 
{} 


Foo::~Foo() {} 


// File Main.cpp 
#include "Foo.h" 
#include "Bar.h" 

int main() 
{ 
    std::vector<double> v(42); 
    Foo f; 
    Bar b(v.begin(), v.end()); 
} 

Przy sporządzaniu tego przykładu z Visual Studio 2005 SP1, otrzymuję błąd z Bar ale nie z Foo:

1>Compiling... 
1>main.cpp 
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(32) : error C2027: use of undefined type 'Bar::Impl' 
1>  c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(15) : see declaration of 'Bar::Impl' 
1>  c:\users\boost_1_45_0\boost\smart_ptr\scoped_ptr.hpp(80) : see reference to function template instantiation 'void boost::checked_delete<T>(T *)' being compiled 
1>  with 
1>  [ 
1>   T=Bar::Impl 
1>  ] 
1>  c:\users\boost_1_45_0\boost\smart_ptr\scoped_ptr.hpp(76) : while compiling class template member function 'boost::scoped_ptr<T>::~scoped_ptr(void)' 
1>  with 
1>  [ 
1>   T=Bar::Impl 
1>  ] 
1>  c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(16) : see reference to class template instantiation 'boost::scoped_ptr<T>' being compiled 
1>  with 
1>  [ 
1>   T=Bar::Impl 
1>  ] 
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(32) : error C2118: negative subscript 
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(34) : warning C4150: deletion of pointer to incomplete type 'Bar::Impl'; no destructor called 
1>  c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(15) : see declaration of 'Bar::Impl' 

postaram to z niedawnym gcc jak tylko wrócę do domu.

Nie rozumiem, co się dzieje: w miejscu zdefiniowania destruktora (tj. W Bar.cpp) dostępna jest definicja Bar::Impl, więc nie powinno być problemu. Dlaczego to działa z Foo, a nie z Bar?

Czego mi tu brakuje?

Odpowiedz

5

To jest destruktor z boost::shared_ptr<>, który wymaga, aby obiekt był kompletny podczas używania deltera w wersji boost::checked_deleter<>. Ponieważ umieścisz konstruktor zakresu Bar::Bar(I begin, I end) w pliku nagłówkowym, kompilator musi wygenerować kod, który niszczy już zbudowane elementy, jeśli konstruktor je wyrzuci, dlatego próbuje utworzyć egzemplarz boost::scoped_ptr<T>::~scoped_ptr(void) podczas tworzenia tego konstruktora szablonu.

Używanie inteligentnych wskaźników za pomocą pimpl jest przydatne. Ponieważ zwykle musisz dostarczyć destruktor tak czy inaczej, możesz równie dobrze umieścić w tym destruktorze delete pimpl i zrobić z tym.

+0

OK, widzę, czego mi brakowało. Pytanie brzmi: uproszczenie zabawek w rzeczywistym świecie, który mi się dziś przytrafił. Właściwą klasą wskaźnika jest 'boost :: shared_ptr', więc nie mogę tu mieć gołego wskaźnika. Zbadam, używając niestandardowego deletera. Dzięki. –

+3

Nie zgadzam się z mniej niż pożytecznymi. ** Ponieważ ** użycie 'scoped_ptr' problem jest podświetlony przez kompilator, jeśli zapomnisz napisać destruktor, także ciało destruktora jest niezwykle proste (puste ...). Z drugiej strony, używając surowego wskaźnika, nie zostaniesz powiadomiony i będziesz musiał zadzwonić. Ponadto, użycie 'shared_ptr', dzięki wbudowanemu deletrowi, faktycznie uwalnia cię od ciężaru pisania destruktora w ogóle. –

+0

@Matthieu: To kwestia gustu, preferuję prosty i potężny kod, nie ma niepotrzebnych elementów tylko dla wygody. –

1

Od Boost Boost documentation:

Zauważ, że scoped_ptr wymaga T być kompletnym typu w momencie zniszczenia, ale shared_ptr nie.

Przełącz na shared_ptr i wszystko powinno być dobrze - nie trzeba mieć destruktora (pustego lub innego). Jeśli chcesz uczynić klasę nieodwzorowalną, aby miała semantykę, którą dostałeś od scoped_ptr, dziedzicz (prywatnie) z boost :: noncopyable.