最近在实现某个模块加强了对 pImpl
设计模式的理解,这里记录下来备忘。
pImpl
模式用来将接口与实现分离,更改实现的话只需要重新编译 cpp 实现,不影响依赖其抽象的模块,也就是著名的桥接模式。
然而在实践中发现有很多类嵌套类,这种也算是 pImpl
的体现,但是做的不够完美,因为这些嵌套类直接定义在头文件中,头文件需要暴露出去,其他模块自然也会看到这些内部嵌套类的定义,影响依赖关系。比如:
// Module.h
class Module {
public:
...
private:
class InternalModule { // Module 部分实现委托到 InternalModule 内部类
...
} m_module;
};
这个模式核心所在是在头文件通过前向声明class Impl
,并通过指针指向实例,然后在 cpp 中定义并实现Impl
。
// Person.h
#include <memory>
class Person {
public:
Person(std::string name); // (1)
~Person(); // (2)
void Dosomething();
...
private:
struct Impl; // (3)
std::unique_ptr<Impl> pImpl; // (4)
};
// Person.cpp
#include "Person.h"
class Person::Impl { // impl 定义与实现
public:
Impl(std::string name): name(name) { }
void Dosomething() {
// ...
}
private:
std::string name;
};
void Person::Dosomething() {
return pImpl->Dosomething();
}
// 构造、析构函数必须放在实现 cpp 里,避免 inline constructor/destructor 导致的 incomplete type `Impl' 问题
Person::Person(std::string name):
pImpl(new Impl{name}) // 编程规范问题,可以放到 Init()函数中初始化
{ }
Person::~Person() = default;
需要注意几个点,由于采用 std::unqiue_ptr
托管 pImpl
,而std::unqiue_ptr
的声明如下:
template <typename _Tp, typename _Dp = default_delete<_Tp>>
class unique_ptr;
其中 default_delete
模板实例化需要看到 Impl
的定义,而其定义在 cpp 中,导致构造、析构函数必须得放到 cpp 中:
- (1) 构造函数需要在头文件中声明,cpp 中实现,而初始化
pImpl
可以放到 Init 函数中 - (2) 析构函数同上
- (3) 在类内前向声明
Impl
,避免暴露实现出去 - (4) 使用
std::unique_ptr
托管pImpl
。