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?
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. –
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. –
@Matthieu: To kwestia gustu, preferuję prosty i potężny kod, nie ma niepotrzebnych elementów tylko dla wygody. –