前言
在日常开发中,类的public方法提供了对外接口供第三方使用,每个函数的具体实现都在XXX.cpp里,对第三方不可见。对于Windows系统上提供给第三方的库,库作者一般需要提供.h、.lib、.dll 文件给使用者,对于Linux系统则需要提供.h、.a、或 .so。
不管那种系统,提供像XXX.h这样的头文件给第三方使用时, .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;
// 前置声明
class Impl;
class Object
{
public:
Object();
~Object();
private:
Impl* m_pImpl;
};
class Impl
{
public:
Impl()
{
// 可以做一些初始化工作
}
~Impl()
{
// 可以做一些清理工作
}
public:
char a;
short b;
int c;
long d;
long long e;
float f;
double g;
};
接着在.cpp文件中,Object类的构造函数创建这个m_mImpl对象,在析构函数释放该对象。
Object::Object()
{
m_pImpl = new Impl();
}
Object::~Object()
{
delete m_pImpl;
}
对于之前的直接引用成员变量,现在可以使用m_pImpl->操作来引用了。
定义成内部类
在实际开发中,由于Impl类是Object类的辅助类,所以可以将Impl类定义成Object的内部类,如下:
class Object
{
public:
Object();
~Object();
private:
class Impl;
Impl* m_pImpl;
};
然后在Object.cpp中定义Impl的实现
class Object::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;
}
现在Object这个类除了保留对外的接口,其内部实现用到的变量和方法基本对使用者不可见了。C++中对类的这种封装方法称为pimpl惯用法,即Ponter to Implementation.
使用智能指针来管理
C++11标准引入了智能指针对象,我们可以使用std::unique_ptr对象来管理上述用于隐藏具体实现的m_pImpl指针。修改后:
#include<memory>
class Object
{
public:
Object();
~Object();
private:
struct Impl;
std::unique_ptr<Impl> m_pImpl;
};
class Object::Impl
{
public:
Impl()
{
// 可以做一些初始化工作
}
~Impl()
{
// 可以做一些清理工作
}
public:
char a;
short b;
int c;
long d;
long long e;
float f;
double g;
};
Object::Object()
{
// C++11中未提供std::make_unique方法,是C++14提供的
m_pImpl.reset(new Impl());
}
// C++14及以上
Object::Object()
: m_pImpl(std::make_unique<Impl>())
{
}
Object::~Object()
{
// 不需要显式删除了
//delete m_pImpl;
}
小结
在实际开发中,Impl类的声明和定义可以使用class关键字,也可以使用struct关键字。struct所有的成员变量和方法默认都是public的。
pimpl惯用法的优点:
- 核心数据成员被隐藏,不必暴露在头文件中,提高了安全性。
- 降低了编译依赖,提高编译速度。
- 接口与实现分离,使用pimpl惯用法之后,即使Object或Impl类的实现细节发生改变,对使用者都是透明的,对外的Object类声明却仍然保持不变。