一、背景
1.概述
PIMPL 是 C++ 中的一个编程技巧,意思为指向实现的指针。具体操作是把类的实现细节放到一个单独的类中,并用一个指针进行访问。
2.二进制兼容性
(1) 概述
二进制兼容是指当库文件升级后所有使用该库的应用程序不必重新编译,其本质就是类的内存布局不变。使用 pimpl 方法设计类可以实现二进制兼容的目的。
(2) 类成员更改后的内存布局
原始类定义:
class demoClass
{
private:
int a;
int b;
};
内存布局如下:
类更改后的定义:
class demoClass
{
private:
char c;
int a;
int b;
};
内存布局如下:
(3) pimpl 下类的内存布局
class demoClass
{
private:
class demoClassImpl;
demoClassImpl* impl;
};
class demoClass::demoClassImpl
{
public:
int a;
int b;
};
内存布局如下:
如上图所示,无论类的实现类的数据成员如何变化,类的布局始终不变。
二、pimpl 应用
1.功能实现细节隐藏
(1) 概述
作为接口的提供者,我们希望接口的使用者不必知道接口实现的更多细节,因为根据类的私有数据成员和方法一般就可以猜测出接口的设计方式。
(2) 隐藏实现细节
通过 pimp 方法设计类可以实现隐藏类的私有成员和方法的目的,仅对外暴露公有的接口。
class demoClass
{
public:
void func();//对外接口
private:
class demoClassImpl;
demoClassImpl* impl;
};
class demoClass::demoClassImpl
{
private:
int a;
int b;
void func1();
void func2();
public:
void func();
};
void demoClass::func()
{
impl->func();
}
2.降低编译依赖
(1) 概述
在一个常用的头文件中如果包含了太多其他不必要的头文件会严重降低编译效率。
(2) 值类型的成员必须引用其头文件
值类型的成员因为要分配内存大小必须知道其确定的定义,需要包含其头文件
#include "A.h"
class demoClass
{
A a;
};
如果仅有类的申明则会出错:
class A;
class demoClass
{
A a;
};
(3) 指针或者引用类型,仅需要类的申明
class A;
class demoClass
{
A func(A a);
};
(4) 使用 pimpl 降低编译依赖
一般库文件使用者仅需要包含当前库对应的头文件即可,不应该再包含其他的头文件。假设库的头文件定义如下:
#include "A.h"
class demoClass
{
private:
A a;
public:
void func();
};
此时,若 A 为另外一个公共库,则库的使用者需要在项目中配置 A.h 的路径;若 A 为自定义类,则库的提供者还需要额外提供 A.h 文件。
使用 pimpl 方法改进则可以减少编译依赖,仅在类的实现文件中包含头文件即可:
// demoClass.h
class demoClass
{
public:
void func();//对外接口
private:
class demoClassImpl;
demoClassImpl* impl;
};
// demoClass.cpp
#include "A.h"
class demoClass::demoClassImpl
{
private:
A a;
public:
void func();
};
2.动态配置功能的实现方法
(1) 概述
使用 pimpl 的方式把类的功能实现用另外一个独立的类来完成,可以在需要的时候动态的配置类的实现方法,而保持类的接口不变。
(2) 代码示例
公共接口类:
class demoClassImpl;
class demoClass
{
public:
void func();//对外接口
public:
demoClassImpl* impl;
};
void demoClass::func()
{
impl->func();
}
功能实现抽象类:
class demoClassImpl
{
public:
virtual void func() = 0;
};
功能实现派生类:
class demoClassImpl1 : public demoClassImpl
{
public:
void func() { cout func();