在日常开发中,类的public方法提供了对外接口供第三方使用,每个函数的具体实现都在XXX.cpp里,对第三方不可见。对于提供给第三方的库,库作者一般需要提供.h文件给使用者。提供像XXX.h这样的头文件给第三方使用时, .h文件中类的大量成员变量和私有函数都暴露了这个类的太多实现细节,很容易让使用者看出其实现原理。
//xxxx.h
class Object
{
public:
Object();
~Object();
private:
char a;
short b;
int c;
long d;
long long e;
float f;
double g;
};
使用pimpl惯用法之后
所有的关键成员变量都已经不存在了,取而代之的时一个类型为Impl的指针成员变量m_pImpl;
//xxxx.h
// 前置声明
class Impl;
class Object
{
public:
Object();
~Object();
private:
Impl* m_pImpl;
};
//xxxx.cpp
class Impl
{
public:
Impl()
{
// 可以做一些初始化工作
}
~Impl()
{
// 可以做一些清理工作
}
public:
char a;
short b;
int c;
long d;
long long e;
float f;
double g;
};
Object::Object()
{
m_pImpl = new Impl();
}
Object::~Object()
{
delete m_pImpl;
}
对于之前的直接引用成员变量,现在可以使用m_pImpl->操作来引用了。
现在Object这个类除了保留对外的接口,其内部实现用到的变量和方法基本对使用者不可见了。C++中对类的这种封装方法称为pimpl惯用法,即Ponter to Implementation.
在实际开发中,Impl类的声明和定义既可以使用class关键字,也可以使用struct关键字。在C++中,struct类型可以用于定义成员方法,但struct所有的成员变量和方法默认都是public的。
现在总结该方法的优点,如下所述。
◎ 核心数据成员被隐藏,不必暴露在头文件中,对使用者透明,提高了安全性。
◎ 降低了编译依赖,提高了编译速度。原来头文件中的一些私有成员变量可能是非指针、非引用类型的自定义类型,需要在当前类的头文件中包含这些类型的头文件。在使用了 pimpl 惯用法以后,这些私有成员变量就被移动到当前类的 cpp 文件中,因此头文件不再需要包含这些成员变量的类型头文件,当前头文件变得“干净”,其他文件在引用这个头文件时,依赖的类型变少,加快了编译速度。
◎ 接口与实现分离。使用了 pimpl 惯用法之后,即使Object或者 Impl 类的实现细节发生了变化,对使用者都透明,对外的Object类声明却仍然可以保持不变。例如,我们可以增、删、改 Impl 的成员变量和成员方法,而保持Object.h文件的内容不变;如果不使用pimpl惯用法,则我们做不到不改变Object.h文件而增、删、改Object类的成员。