Pimpl设计思想
前言
Pimpl Idiom (Pointer to implementation, 指向实现的指针) 设计模式的核心思想是将类的实现细节隐藏在一个独立的内部类,这样就减少了编译依赖且增强二进制兼容性。这种开发模式也被称之为D-Pointer (D指针)。
特点
- 编译依赖性 : 如果类的实现 (成员变量,私有方法之类的) 频繁的迭代变动,那不可避免的会导致头文件也会随着变更,导致所有的包含该头文件的代码都需要通过重新编译被使用。如果将细节移动到一个Private类且定义在对应的.cpp文件中,头文件就只需要声明一个指针,避免了暴露实现细节;最后只需要重新编译被变动的.cpp文件之后就可以加载到所用模块。
- 更好的封装 : 头文件最后只暴露了一个接口,符合《最小暴露原则》。工程师也无法直接访问Private类的成员,只保留公有方法提供操作。
- 二进制兼容 : 如果库中的私有成员有变动 (例如:不需要使用名称了,添加了新的年龄属性和ID属性) 且保证Private类的指针大小不变就能保证库的二进制链接接口(ABI)可以稳定,能保证动态链接库(dll/so)升级稳定兼容。
例如:
//object.h
class Object {
private:
class Private;
Private* _Prv_ptr;
}
//或者
#define DECLARE_PRIVATE_OBJECT class Private; Private* _Prv_ptr;
class Object {
DECLARE_PRIVATE_OBJECT
}
//更先进的方案
class Object {
private:
struct Private;
std::unique_ptr<Private> m_private;
}
/******************************************/
//object.cpp
class Object::Private {
std::string name;
//...
}
Pimpl怎么保证ABI稳定?
变化类型 | 无Pimpl | 有Pimpl |
---|---|---|
修改私有成员变量 | 头文件被改动 | 仅仅更改.cpp,依赖项无需重新编译 |
新增&删除私有成员变量 | ABI破坏(所占内存变更) | ABI稳定(指向的指针大小不变) |
修改私有方法 | 头文件被改动 | 仅仅更改.cpp,依赖项无需重新编译 |
指向实现的D指针,他只是一个指针(32位系统上是4B,64位系统上是8B);无论这个Iimpl类内部如何变化,都保证了所指向的指针不会变;
Pimpl的缺点
- 访问开销:每次访问私有变量都需要通过D指针跳转(忽略不计)
- 内存管理:持有了PImpl的D指针就需要负责到底生命周期 (可以通过std::unique_ptr < Private > 解放大脑)
身边的影子
Qt的QPointer广泛使用类似的Q_DECLARE_PRIVATE设计模式
QT宏: Q_DECLARE_PRIVATE与Q_DECLARE_PUBLIC(d指针 \ p指针) 及其优化使用Q_Q宏和Q_D宏