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?
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. –
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. –
@Matthieu: È una questione di gusti, io preferisco il codice semplice e potente, non inutile incluso solo per il comfort di esso. –