考虑以下:
PImpl.hpp
class Impl;
class PImpl
{
Impl* pimpl;
PImpl() : pimpl(new Impl) { }
~PImpl() { delete pimpl; }
void DoSomething();
};
Run Code Online (Sandbox Code Playgroud)
PImpl.cpp
#include "PImpl.hpp"
#include "Impl.hpp"
void PImpl::DoSomething() { pimpl->DoSomething(); }
Run Code Online (Sandbox Code Playgroud)
Impl.hpp
class Impl
{
int data;
public:
void DoSomething() {}
}
Run Code Online (Sandbox Code Playgroud)
client.cpp
#include "Pimpl.hpp"
int main()
{
PImpl unitUnderTest;
unitUnderTest.DoSomething();
}
Run Code Online (Sandbox Code Playgroud)
这种模式背后的想法是,Impl界面可以改变,但客户端不必重新编译.然而,我没有看到这是如何真实的情况.假设我想在这个类中添加一个方法 - 客户端仍然需要重新编译.
基本上,只有种这样的,我可以看到的变化不断需要改变头文件一类的东西,其中的类发生变化的接口.当发生这种情况时,pimpl或没有pimpl,客户端必须重新编译.
这里的哪种编辑在不重新编译客户端代码方面给我们带来了好处?
Ala*_*lan 10
主要优点是接口的客户端不必强制包含所有类的内部依赖项的标头.因此,对这些标题的任何更改都不会级联到大多数项目的重新编译中.加上关于实现隐藏的一般理想主义.
此外,您不一定会将您的impl类放在自己的标头中.只需将它作为单个cpp中的结构,并使外部类直接引用其数据成员.
编辑: 示例
SomeClass.h
struct SomeClassImpl;
class SomeClass {
SomeClassImpl * pImpl;
public:
SomeClass();
~SomeClass();
int DoSomething();
};
Run Code Online (Sandbox Code Playgroud)
SomeClass.cpp
#include "SomeClass.h"
#include "OtherClass.h"
#include <vector>
struct SomeClassImpl {
int foo;
std::vector<OtherClass> otherClassVec; //users of SomeClass don't need to know anything about OtherClass, or include its header.
};
SomeClass::SomeClass() { pImpl = new SomeClassImpl; }
SomeClass::~SomeClass() { delete pImpl; }
int SomeClass::DoSomething() {
pImpl->otherClassVec.push_back(0);
return pImpl->otherClassVec.size();
}
Run Code Online (Sandbox Code Playgroud)
有很多答案......但到目前为止还没有正确实施.我有点难过,因为人们可能会使用它们,所以例子不正确......
"Pimpl"成语是"指向实现的指针"的缩写,也称为"编译防火墙".现在,让我们潜入.
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);
};
Run Code Online (Sandbox Code Playgroud)
在上面的例子中,包括"方便"包括并且可以删除而不影响正确性?最令人惊讶的是:除了"啊"之外.
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); }
Run Code Online (Sandbox Code Playgroud)
其他人没有的是什么?
T应该抛出......但是,这是一个非常常见的要求;)在此基础上,我们现在可以轻松地定义Pimpl的类:
class Foo
{
public:
private:
struct Impl;
pimpl<Impl> mImpl;
}; // class Foo
Run Code Online (Sandbox Code Playgroud)
注意:编译器无法在此生成正确的构造函数,复制赋值运算符或析构函数,因为这样做需要访问Impl定义.因此,尽管pimpl帮手,您将需要手动不过来定义那些4,多亏了平普尔助手编译将会失败,而不是你拖到未定义行为的土地.
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;
}
Run Code Online (Sandbox Code Playgroud)
然后我们就可以定义我们Foo像这样
// 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)) {}
Run Code Online (Sandbox Code Playgroud)
请注意,ABI Foo完全不受可能发生的各种变化的影响:
FoomImpl是一个简单的指针,无论它指向什么因此,您的客户不必担心会添加方法或属性的特定补丁,您不必担心内存布局等......它只是自然有效.
使用PIMPL惯用法,如果IMPL类的内部实现细节发生更改,则不必重建客户端.IMPL(以及头文件)类的接口的任何更改显然都需要更改PIMPL类.
BTW,在所示的代码中,IMPL和PIMPL之间存在强耦合.因此,IMPL的类实现中的任何更改也会导致需要重建.
| 归档时间: |
|
| 查看次数: |
2213 次 |
| 最近记录: |