何为 pimpl ?

前言

你是否总因头文件包含冲突而苦恼?
你是否因头文件包含错乱而苦恼?
你是否因封装暴露了数据而苦恼?
你是否因经常改动实现而导致重新编译而苦恼?

在这里, 这些问题都不是问题, 跟随作者, 揭秘pimpl.

正文

先来看一段例子:

有A, B 2个类, 分别由A.h, A.cpp, B.h, B.cpp文件实现.

同时, A类中包含了B类成员, B类中包含了A类成员.

 1 // A.h

 2 #include "B.h"

 3 class A {

 4 private:

 5     B b;

 6 };

 7 

 8 // B.h

 9 #include "A.h"

10 class B {

11 private:

12     A a;

13 };

你是否一眼就看出了问题!

是的, 头文件互相包含了, 那怎么解决此问题?

解决方案?

// A.h

class B;



class A {

public:

    A();

    ~A();

private:

    B *pB;

};



// A.cpp

#include "B.h"

A::A(): pB(new B())

{

}

A::~A()

{

    delete pB;

}



// B.h

class A;



class B {

public:

    B();

    ~B();

private:

    A *pA;

};



// B.cpp

#include "A.h"

B::B(): pA(new A())

{

}

B::~B()

{

    delete pA;

}

我们在头文件中互相前置声明, 这种行为是"不要钱"的, 大伙可以尽情的用.

随后在源文件中, 互相包含头文件, 在各自的构造函数中new 出对象, 析构函数中 delete 对象.

仅此而已?

接下来才是pimpl的重度解说.

假设我们的A需要实现成员函数 AmemberFunc.

这个成员函数过程复杂, 可能需要分割成多个小函数.

因此就有了一下清单:

func1();

func2();

...

funcN();

过程中还需要一系列成员变量.

因此就有了一下清单:

type1 var1, var2 ... varN;

type2 var1, var2 ... varN;

...

typeN var1, var2 ... varN;

注意, 你需要的仅仅是使用A的AmemberFunc接口.

其他的任何东西在你眼里都是累赘, 他们并不能让你更清楚实现, 反而扰乱你的思绪.

 

当你使用一个只有一个接口的对象时, 查看其头文件, 发现一堆的成员清单, 

如果你胆子比较大, 或许会去源文件查看清单中的东西到底都在干什么.

这个时候的你已经走向歧途了.

当然, 也可能源文件已经被编译成了库文件, 你的壮志雄心不得已施展.

再次注意, 你需要的仅仅是这个接口, 至于接口的实现... 除非你的目的是把这个接口一探究竟, 否则纯属浪费时间.

废话说了不少, 看看解决方案.

// A.h

class A {

public:

    A();

    ~A();

    type memberFunc();

private:

    class impl;

    impl *pimpl;

};



// A.cpp

#include "B.h"

class A::impl {

    type memberFunc()

    {

        ...

    }

private:

    func1();

    func2();

    ...

    funcN();

    

    type1 var1, var2 ... varN;

    type2 var1, var2 ... varN;

    ...

    typeN var1, var2 ... varN;

};





A::A(): pimpl(new impl())

{



}



A::~A()

{

    delete pimpl;

}

type A::memberFunc()

{

    return pimpl->memberFunc();

}

 A的内部声明了一个impl类型.

该类实例负责实现A的所有功能.

A只需调用impl相应的函数.

此方法隐藏了所有不需要暴露的细节.

并且避免了头文件的依赖. (因为实现在源文件中, 你可以在里面包含任意头文件不必考虑冲突问题.)

增强了可读性, 用户不会被"细节"扰乱思绪.

尾声

pimpl的功能远不止这些, 你可以在pimpl中抛出异常, 在外层类中捕获异常, 从而让异常完全透明.

还有更多功能, 等着你去发现..

 

当然, pimpl的缺陷也是显而易见的.

那就是不能内联! 不过谁会在意呢.

你可能感兴趣的:(imp)