1.简介
这个机制是Private Implementation的缩写,我们常常听到诸如“不要改动你的公有接口”这样的建议,所以我们一般都会修改私有接口,但是这会导致包含该头文件的所有源文件都要重新编译,这会是个麻烦事儿。Pimpl机制,顾名思义,将实现私有化,力图使得头文件对改变不透明。
2.机制分析
首先,我们先看看不使用这个机制的一个实现:
1: // MyBase.h
2: class MyBase {
3: public:
4: int foo();
5: };6:7: // MyDerived.h
8: #include "MyBase.h"
9: class MyDerived : public MyBase {10: public:
11: int bar();
12: };
假设你现在希望在MyBase.h中加入一个新的private和protected成员函数,那么MyDerived和所有包含MyBase.h的源文件都需要重新编译。在一个大工程中,这样的修改可能导致重新编译时间的激增。你可以使用Doxygen或者SciTools看看头文件依赖。
一般来说,不在头文件中包含头文件是一个比较好的习惯,但是这也不能完全消除修改MyBase.h带来的重新编译代价。有没有一个机制可以使得对私有接口做修改时我们可以减小重新编译的代价。
在Pimpl机制中,我们使用前置声明一个Impl类,并将这个类的一个指针实例放入主类中,如下:
1: // MyClass.h
2: class MyClassImpl; // forward declaration3: class MyClass {
4: public:
5: MyClass();6: ~MyClass();7: int foo();
8: private:
9: MyClassImpl *m_pImpl;10: };
现在,除非我们修改MyClass的公有接口,否则这个头文件是不会被修改了。然后,我们用这个Impl类的实现来完成主类的细节实现,在主类的构造函数中,我们完成了实现类指针的实例化:
1: // MyClass.cpp
2: class MyClassImpl {
3: public:
4: int foo() {
5: return bar();
6: }7: int bar() { return var++; }8: int var;
9: };10:11: MyClass::MyClass() : m_pImpl(new MyClassImpl){}
12:13: MyClass::~MyClass()14: {15: try {
16: delete m_pImpl;
17: }18: catch (...) {}
19: }20:21: int MyClass::foo(){ return m_pImpl->foo(); }
Pimpl机制其实这是桥接模式的一种变种。我们可以对实现类随意的进行增删和修改,而不会导致包含MyClass.h的源代码重新编译。当然,这样做的时间开销和空间开销也是有的。
在实践中,我们常常采用内部类来完成Pimpl机制:
1: // header
2: class fruit
3: {4: public:
5:6: private:
7: class impl;
8: impl* pimpl_;9: }10:11: // implementation
12: class fruit::impl
13: {14:15: };16:17: fruit::fruit()18: {19: pimpl_ = new impl();
20: }