PIMPL是“Pointer to IMPLementation”的缩写,它是一种编译防火墙——一种防止修改一个头文件会触发许多源文件被重编译的机制———的编程惯用法。
下面举一个例子,假设有一个类BigClass,它有一些内联函数,且和一些其他的类有使用关系(Foo类、Bar类、Baz类等),代码如下:
//bigclass.h
#include "foo.h"
#include "bar.h"
#include "baz.h"
class BigClass{
public:
BigClass();
void f1(int a) {...}
void f2(float f) {...}
Foo foo;
Bar bar;
Baz baz;
};
这样写是有一个问题的,那就是一旦bigclass.h、foo.h、bar.h或是baz.h的任何地方发生了改动,都会导致引用bigclass.h的文件被重编译,如果工程比较大,BigClass被很多文件引用的话,就会导致编译时间边长。
为了解决这个问题,我们可以使用一个过渡类Impl来当一个中间商的角色:
//bigclass.h
class Impl;
class BigClass{
public:
Bigclass();
void f1(int a);
char f2(float f);
Impl * impl;
}
C++允许声明一个指向未完成类型的指针,在上面的代码中,Impl就是一个未完成类型。它的实现放在cpp文件中:
//bigclass.cpp
#include "foo.h"
#include "bar.h"
#include "baz.h"
#include "bigclass.h"
class Impl{
void g1(int a);
char g2(float f);
Foo foo;
Bar bar;
Baz baz;
};
void Impl::g1(int a)
{
...
}
char Impl::g2(float f)
{
...
}
Bigclass::Bigclass()
{
impl = new Impl;
}
void BigClass::f1(int a)
{
impl->g1(a);
}
通过这样实现,在编译时,针对foo.h、bar.h、baz.h或是对Impl的改动都会重新编译bigclass.cpp,但是bigclass.h不会改变,这就限制了重编译的范围。这样引用bigclass.h的文件也不会再重新编译,减少了编译的时间。
这种方法的缺点是会给程序带来延迟,因为在BigClass的声明与实现之间增加了Impl作为中间层,会增加成员函数调用;再加上现在编译器的编译时间已经比之前大大缩短,因此如果不是一个非常大的类,被非常多的文件引用的话,其实现在也没有必要使用该方法。