有很多答案......但到目前为止还没有正确的实施。我有点难过示例不正确,因为人们可能会使用它们......
“Pimpl”成语是“Pointer to Implementation”的缩写,也称为“编译防火墙”。现在,让我们开始吧。
1.什么时候需要包含?
使用类时,仅在以下情况下才需要其完整定义:
- 你需要它的大小(你的类的属性)
- 您需要访问其中一种方法
如果您只引用它或有一个指向它的指针,那么由于引用或指针的大小不依赖于引用/指向的类型,您只需声明标识符(前向声明)。
例子:
#include "a.h"
#include "b.h"
#include "c.h"
#include "d.h"
#include "e.h"
#include "f.h"
struct Foo
{
Foo();
A a;
B* b;
C& c;
static D d;
friend class E;
void bar(F f);
};
在上面的示例中,哪些包含是“方便”包含并且可以在不影响正确性的情况下删除?最令人惊讶的是:除了“啊”。
2. 实现 Pimpl
因此,Pimpl 的想法是使用一个指向实现类的指针,这样就不需要包含任何头文件:
- 从而将客户端与依赖项隔离开来
- 从而防止编译涟漪效应
另一个好处:保留了库的 ABI。
为了便于使用,Pimpl 成语可以与“智能指针”管理风格一起使用:
// From Ben Voigt's remark
// information at:
// http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Checked_delete
template<class T>
inline void checked_delete(T * x)
{
typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
(void) sizeof(type_must_be_complete);
delete x;
}
template <typename T>
class pimpl
{
public:
pimpl(): m(new T()) {}
pimpl(T* t): m(t) { assert(t && "Null Pointer Unauthorized"); }
pimpl(pimpl const& rhs): m(new T(*rhs.m)) {}
pimpl& operator=(pimpl const& rhs)
{
std::auto_ptr<T> tmp(new T(*rhs.m)); // copy may throw: Strong Guarantee
checked_delete(m);
m = tmp.release();
return *this;
}
~pimpl() { checked_delete(m); }
void swap(pimpl& rhs) { std::swap(m, rhs.m); }
T* operator->() { return m; }
T const* operator->() const { return m; }
T& operator*() { return *m; }
T const& operator*() const { return *m; }
T* get() { return m; }
T const* get() const { return m; }
private:
T* m;
};
template <typename T> class pimpl<T*> {};
template <typename T> class pimpl<T&> {};
template <typename T>
void swap(pimpl<T>& lhs, pimpl<T>& rhs) { lhs.swap(rhs); }
它有什么其他人没有的?
- 它只是遵循三法则:定义复制构造函数、复制赋值运算符和析构函数。
- 它这样做是为了实现强保证:如果副本在分配期间抛出,则对象保持不变。请注意,析构函数不
T
应该抛出......但是,这是一个非常普遍的要求;)
在此基础上,我们现在可以稍微轻松地定义 Pimpl'ed 类:
class Foo
{
public:
private:
struct Impl;
pimpl<Impl> mImpl;
}; // class Foo
注意:编译器无法在此处生成正确的构造函数、复制赋值运算符或析构函数,因为这样做需要访问Impl
定义。因此,尽管有pimpl
帮助程序,您仍需要手动定义这 4 个。但是,由于 pimpl 帮助程序,编译将失败,而不是将您拖入未定义行为的领域。
3. 更进一步
应该注意的是,virtual
函数的存在通常被视为实现细节,Pimpl 的优点之一是我们拥有正确的框架来利用战略模式的力量。
这样做需要更改 pimpl 的“副本”:
// pimpl.h
template <typename T>
pimpl<T>::pimpl(pimpl<T> const& rhs): m(rhs.m->clone()) {}
template <typename T>
pimpl<T>& pimpl<T>::operator=(pimpl<T> const& rhs)
{
std::auto_ptr<T> tmp(rhs.m->clone()); // copy may throw: Strong Guarantee
checked_delete(m);
m = tmp.release();
return *this;
}
然后我们可以定义我们的Foo
like
// foo.h
#include "pimpl.h"
namespace detail { class FooBase; }
class Foo
{
public:
enum Mode {
Easy,
Normal,
Hard,
God
};
Foo(Mode mode);
// Others
private:
pimpl<detail::FooBase> mImpl;
};
// Foo.cpp
#include "foo.h"
#include "detail/fooEasy.h"
#include "detail/fooNormal.h"
#include "detail/fooHard.h"
#include "detail/fooGod.h"
Foo::Foo(Mode m): mImpl(FooFactory::Get(m)) {}
请注意,ABIFoo
完全不关心可能发生的各种变化:
- 没有虚方法
Foo
- 的大小
mImpl
是一个简单指针的大小,无论它指向什么
因此,您的客户不必担心会添加方法或属性的特定补丁,并且您不必担心内存布局等......它自然而然地工作。