2011-02-07 11 views
5

Quando si utilizza puntatori intelligenti con l'idioma Pimpl, come inPimpl con puntatori intelligenti in una classe con un costruttore modello: strano tipo incompleto problema

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

il problema evidente è che Foo::Impl è incompleto nel punto in cui il distruttore di Foo viene generato.

I compilatori di solito emettono un avviso e boost::checked_delete, utilizzato internamente dai puntatori intelligenti Boost, afferma staticamente che la classe Foo::Impl è completa e attiva un errore se non è il caso.

Per l'esempio precedente per compilare, si deve quindi scrivere

struct Foo 
{ 
    ~Foo(); 

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

e implementare un vuoto Foo::~Foo nel file di implementazione, dove Foo::Impl è completa. Questo è un vantaggio dei puntatori intelligenti rispetto ai puntatori nudi, perché non possiamo fallire nell'implementare il distruttore.

Finora, tutto bene. Ma mi sono imbattuto in un comportamento strano quando si tenta di introdurre un costruttore di modello in un simile Bar classe (codice completo, provate voi stessi):

// 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()); 
} 

Quando si compila questo esempio con Visual Studio 2005 SP1, ottengo un errore Bar ma non con 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' 

cercherò questo con un recente gcc non appena torno a casa.

Non capisco cosa sta succedendo: nel punto in cui è definito il distruttore (ad esempio in Bar.cpp), è disponibile una definizione di Bar::Impl, quindi non ci dovrebbero essere problemi. Perché funziona con Foo e non con Bar?

Cosa mi manca qui?

risposta

5

Questo è il distruttore di boost::shared_ptr<> che richiede che l'oggetto sia completo quando si utilizza il deleter boost::checked_deleter<>. Dato che nel file di intestazione si inserisce il costruttore di intervallo Bar::Bar(I begin, I end), il compilatore deve generare codice che distrugge i membri già costruiti se il costruttore esegue il lancio, quindi sta tentando di creare un'istanza di boost::scoped_ptr<T>::~scoped_ptr(void) durante l'istanziazione di questo costruttore di template.

Non è utile utilizzare puntatori intelligenti con Pimpl. Dato che normalmente è necessario fornire un distruttore, si potrebbe anche inserire delete pimpl in quel distruttore e farlo con quello.

+0

Ok, vedo cosa mi mancava. La domanda è una semplificazione giocattolo di un problema reale che mi è successo oggi. La classe del puntatore usata è 'boost :: shared_ptr', quindi non posso avere un puntatore nullo qui. Indagherò usando un deleter personalizzato. Grazie. –

+3

Non sono d'accordo con il meno che utile. ** Poiché ** l'uso di un 'scoped_ptr' il problema è evidenziato dal compilatore se si dimentica di scrivere il distruttore, anche il corpo del distruttore è estremamente semplice (vuoto ...). D'altra parte, usando un puntatore raw non verrai avvisato e dovresti effettuare la chiamata. Inoltre, l'uso di 'shared_ptr', grazie al deleter incorporato, in realtà ti libera dal peso di scrivere un distruttore. –

+0

@Matthieu: È una questione di gusti, io preferisco il codice semplice e potente, non inutile incluso solo per il comfort di esso. –

1

Dal Boost Boost documentation:

noti che scoped_ptr richiede che T un tipo completo al momento della distruzione, ma shared_ptr non lo fa.

Passare a shared_ptr e tutto andrebbe bene, non è necessario avere un distruttore (vuoto o altro). Se vuoi rendere la classe non percorribile in modo che abbia la semantica che avresti ottenuto da scoped_ptr, eredita (in privato) da boost :: noncopyable.

Problemi correlati