在带有模板构造函数的类中使用智能指针的 Pimpl:奇怪的不完整类型问题
当将智能指针与 pImpl 习惯用法一起使用时,
struct Foo
{
private:
struct Impl;
boost::scoped_ptr<Impl> pImpl;
};
明显的问题是 Foo::Impl
在生成 Foo
的析构函数时不完整。
编译器通常会在那里发出警告,而 Boost 智能指针在内部使用的 boost::checked_delete
静态断言类 Foo::Impl
已完成并触发如果不是这种情况,则会出错。
因此,为了编译上面的示例,必须
struct Foo
{
~Foo();
private:
struct Impl;
boost::scoped_ptr<Impl> pImpl;
};
在实现文件中编写并实现一个空的 Foo::~Foo
,其中 Foo::Impl
已完成。这是智能指针相对于裸指针的优势,因为我们不能不实现析构函数。
到目前为止,一切都很好。但当我尝试在类似的 Bar
类中引入模板构造函数时,我遇到了一个奇怪的行为(完整代码,请自行尝试):
// 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());
}
当使用 Visual Studio 2005 SP1 编译此示例时,我得到一个Bar
出错,但 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'
我一回家就会用最近的 gcc 尝试这个。
我不明白发生了什么:在定义析构函数的地方(即在 Bar.cpp
中),可以使用 Bar::Impl
的定义,所以应该不会有任何问题。为什么这适用于 Foo
而不适用于 Bar
?
我在这里缺少什么?
When using smart pointers with the pImpl idiom, as in
struct Foo
{
private:
struct Impl;
boost::scoped_ptr<Impl> pImpl;
};
the obvious problem is that Foo::Impl
is incomplete at the point where the destructor of Foo
is generated.
Compilers usually emit a warning there, and boost::checked_delete
, which is used internally by Boost smart pointers, statically asserts that the class Foo::Impl
is complete and triggers an error if it is not the case.
For the above example to compile, one therefore must write
struct Foo
{
~Foo();
private:
struct Impl;
boost::scoped_ptr<Impl> pImpl;
};
and implement an empty Foo::~Foo
in the implementation file, where Foo::Impl
is complete. This is an advantage of smart pointers over bare pointers, because we can't fail to implement the destructor.
So far, so good. But I came across a weird behaviour when I try to introduce a template constructor in a similar Bar
class (full code, please try it yourself):
// 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());
}
When compiling this example with Visual Studio 2005 SP1, I get an error with Bar
but not with 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'
I will try this with a recent gcc as soon as I get home.
I don't understand what is going on: at the point where the destructor is defined (ie. in Bar.cpp
), a definition of Bar::Impl
is available, so there should not be any problem. Why does this work with Foo
and not with Bar
?
What am I missing here ?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这是::~scoped_ptr(void) 实例化此模板构造函数时。
boost::shared_ptr<>
的析构函数,要求在使用boost::checked_deleter<>
删除器时对象是完整的。因为您将范围构造函数Bar::Bar(I begin, I end)
放在头文件中,所以如果构造函数抛出异常,编译器必须生成销毁已构造成员的代码,因此它会尝试实例化 < code>boost::scoped_ptr将智能指针与 pimpl 一起使用不太有用。由于您通常需要提供一个析构函数,因此您也可以将
delete pimpl
放入该析构函数中并完成此操作。This is the destructor of
boost::shared_ptr<>
that requires the object to be complete when usingboost::checked_deleter<>
deleter. Because you put the range constructorBar::Bar(I begin, I end)
in the header file the compiler must generate code that destroys already constructed members if your constructor throws, hence it is trying to instantiateboost::scoped_ptr<T>::~scoped_ptr(void)
when instantiating this template constructor.It is less than useful to use smart pointers with pimpl. Since you normally need to provide a destructor anyway, you could as well put
delete pimpl
in that destructor and be done with that.来自 Boost Boost 文档:
切换到shared_ptr,一切都应该很好——也不需要析构函数(空或其他)。如果您想让类成为不可复制的,以便它具有从scoped_ptr 获得的语义,请从boost::noncopyable 继承(私有)。
From the Boost Boost documentation:
Switch to shared_ptr and all should be well - no need to have a destructor either (empty or otherwise). If you want to make the class noncopyable so it has the semantics you'd have got from scoped_ptr, inherit (privately) from boost::noncopyable.