ROOT
ROOT

pImpl 技巧:接口与实现分离

最近在实现某个模块加强了对 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
支持一下
扫一扫,支持Netcan
  • 微信扫一扫
  • 支付宝扫一扫