Pimpl模式

Pimpl(Pointer to Implementation,指向实现的指针)是一种经典的 C++ 设计模式,它的核心思想是:

把类的具体实现隐藏起来,只在头文件中保留一个指向实现类的指针。

这样可以实现信息隐藏、降低编译依赖、提高编译速度、保持 ABI 稳定

为什么需要 Pimpl?

假设没有使用 Pimpl。

Robot.h

cpp 复制代码
class Robot
{
public:
    void connect();
    void move();

private:
    TcpClient tcp;
    JsonParser parser;
    Logger logger;
    ThreadPool pool;
};

这里 Robot.h 包含了很多头文件:

cpp 复制代码
#include "TcpClient.h"
#include "JsonParser.h"
#include "Logger.h"
#include "ThreadPool.h"

于是:

Robot.h

├── TcpClient.h

├── Logger.h

├── JsonParser.h

└── ThreadPool.h

如果有 100 个 .cpp 文件都包含了 Robot.h

main.cpp

ui.cpp

server.cpp

test.cpp

...

#include "Robot.h"

现在只要修改了:

Logger.h

哪怕只是增加一个成员变量:

class Logger

{

...

int level;

};

那么:

Logger.h 改变

Robot.h 改变

所有 include Robot.h 的 cpp

全部重新编译

大型项目可能重新编译几分钟甚至几十分钟。

Pimpl 怎么解决?

把真正的数据全部放到 .cpp

Robot.h

cpp 复制代码
class RobotImpl;

class Robot
{
public:
    Robot();
    ~Robot();

    void connect();
    void move();

private:
    RobotImpl* impl;
};

头文件现在只有:

Robot

RobotImpl*

根本不知道里面是什么。

Robot.cpp

cpp 复制代码
#include "Robot.h"
#include "TcpClient.h"
#include "Logger.h"
#include "ThreadPool.h"

class RobotImpl
{
public:
    TcpClient tcp;
    Logger logger;
    ThreadPool pool;
};

然后:

cpp 复制代码
Robot::Robot()
{
    impl = new RobotImpl();
}

调用:

cpp 复制代码
void Robot::connect()
{
    impl->tcp.connect();
}

编译依赖发生了什么变化?

以前:

Robot.h

TcpClient.h

Logger.h

JsonParser.h

...

现在:

Robot.h

什么都不知道

只有 Robot.cpp

知道 TcpClient

所以:

Logger.h 修改

Robot.cpp 重新编译

main.cpp 不用重新编译

编译速度提升很多。

更现代的写法

现代 C++ 不建议手写 new/delete

一般使用:

cpp 复制代码
class RobotImpl;

class Robot
{
public:
    Robot();
    ~Robot();

private:
    std::unique_ptr<RobotImpl> impl;
};

构造:

cpp 复制代码
Robot::Robot()
    : impl(std::make_unique<RobotImpl>())
{
}

析构:

cpp 复制代码
Robot::~Robot() = default;

这是目前最常见的 Pimpl 写法。

Pimpl 的工作流程

假设用户调用:

cpp 复制代码
Robot robot;

robot.connect();

流程:

用户

Robot

impl

RobotImpl

TcpClient

TCP连接

真正干活的是:

RobotImpl

Robot 只是一个对外接口。

Pimpl 的优点

1. 隐藏实现细节

用户只能看到:

cpp 复制代码
Robot robot;

看不到:

TcpClient

JsonParser

ThreadPool

有利于封装。

2. 降低编译依赖

这是 Pimpl 最大的价值。

例如:

ThreadPool 修改

只编译 Robot.cpp

不用编译整个项目

3. 保持 ABI 稳定

假设 SDK 发布:

cpp 复制代码
class Robot
{
private:
    RobotImpl* impl;
};

以后:

RobotImpl 增加:

cpp 复制代码
Cache cache;

甚至:

TcpClient

MQTTClient

Robot 类的大小仍然只是一个指针,因此很多情况下不会破坏二进制接口(ABI),用户无需重新编译即可替换新的动态库(具体还取决于平台、编译器和接口变更)。

4. 减少头文件包含

Robot.h

不用:

cpp 复制代码
#include "Logger.h"
#include "TcpClient.h"
#include "JsonParser.h"

只需要:

cpp 复制代码
#include <memory>

Pimpl 的缺点

多了一次间接访问

cpp 复制代码
impl->connect();

相比直接访问成员,会多一次指针解引用。不过对于大多数业务程序,这点性能开销通常可以忽略。

实现稍复杂

需要定义 Impl 类,并正确管理其生命周期

调试稍麻烦

调试时需要进入 RobotImpl 才能看到真正的数据成员

相关推荐
2601_956121974 小时前
二分算法(知识点+题目)
c++·算法
蒙奇·D·路飞-6 小时前
OpenClaw:开源的《合金弹头》精神续作与终极本地部署指南
c++·开源·mfc
小星星闪亮登场7 小时前
2026萌新联赛第三场-- (郑州轻工业大学)
数据结构·c++·经验分享·算法·贪心算法·排序算法·深度优先
梓䈑7 小时前
【用 Vibe Coding 实现的 C++17 在线判题系统】前端开发 + Web 自动化测试
前端·c++·ai编程
冻柠檬飞冰走茶7 小时前
PTA基础编程题目集 7-8超速判断(C++语言实现)
开发语言·数据结构·c++·算法
TunerT_TQ8 小时前
Valhalla 静态工程审阅 #018|MMCV 源码证据驱动评测【大厂开源基础设施特辑】
c++·开源·mfc·#计算机视觉·#商汤科技·#openmmlab·#静态源码审计
别动我齐刘海9 小时前
Day6 unitree_G1人形机器人GMR—— MotionInput
c语言·c++·人工智能·学习·机器学习·机器人·github
Henry Zhu12310 小时前
C++17 `weak_from_this` 详解:让对象安全地观察自己
c++
June`13 小时前
warp shuffle指令
c++·人工智能·算法·cuda
乱七八糟的屋子13 小时前
【C++数值计算】NumCpp超详细入门教程(纯正Numpy语法、零学习成本、C++首选科学计算库、性能压测对比)
c++·数值计算·numpy·高性能计算·科学计算·ndarray