Pimpl(Pointer to Implementation)是一种常用的C++设计模式,用于隐藏类的实现细节、减少编译依赖性和提高封装性。

概述

Pimpl模式主要的思路就是:在实现类的外层套上一层简单的接口类,接口类只含有一个指向实现类的指针,并暴露必要的接口。 注意接口类包含的不是实现类对象,而是实现类指针,否则修改实现类无法做到二进制的稳定性。

Pimpl模式可以达到如下的效果:

  • 隐藏实现细节:实现细节被封装在实现类中,提供给用户的接口类只暴露必要的接口,提高代码的封装性
  • 维护接口稳定性:我们只需要维护暴露在接口类中的接口稳定性即可,实现类的内部可以自由地进行更改
  • 减少编译依赖性:由于接口类的实现细节被隐藏,接口类只含有实现类的指针,对实现类进行的细节改变不会导致依赖于接口类的文件重新编译,从而减少编译时间。

Pimpl模式和继承以及虚函数的作用是不重合的,并不存在相互取代的关系。 和虚函数类似,Pimpl增加了间接的指针调用,这必然会影响到程序的运行效率。

示例

包括三个文件:

  • Demo.h:接口类的声明
  • Demo.cpp:实现类的完整实现,以及接口类的实现
  • main.cpp:使用接口类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// Demo.h
#pragma once
#include <memory>
#include <string>

class Demo {
public:
explicit Demo(std::string name);
~Demo();

void run() const;

private:
class DemoImpl;
std::unique_ptr<DemoImpl> m_impl;
};

// Demo.cpp
#include "demo.h"
#include <iostream>

class Demo::DemoImpl {
public:
explicit DemoImpl(std::string name) : m_name(std::move(name)) {}

void run() const {
std::cout << "call DemoImpl " << m_name << " run()" << std::endl;
}

private:
std::string m_name;
};

Demo::Demo(std::string name)
: m_impl(std::make_unique<DemoImpl>(std::move(name))) {}

Demo::~Demo() = default;

void Demo::run() const { m_impl->run(); }

// main.cpp
#include "demo.h"

int main(int argc, char *argv[]) {
Demo("temp").run();

return 0;
}

运行结果

1
call DemoImpl temp run()