pimpl

PIMPL 是什么

PIMPL = Pointer to Implementation ,也叫 d-pointer、编译期防火墙(compilation firewall) 核心思想:

把类的实现细节全部放到另外一个内部类里,对外暴露的类只保留一个指向实现类的裸指针 / 智能指针。对外头文件只放接口,不放成员变量、私有函数、依赖头文件。

解决什么痛点

  1. 减少编译依赖 :修改实现 .cpp 里的私有成员,不需要重新编译包含这个头文件的其他代码;
  2. 隐藏内部实现:对外头文件看不到私有数据结构,适合做 SDK、闭源库;
  3. 稳定 ABI:版本迭代时,对外类的内存布局不变,二进制兼容更好;
  4. 缺点:多一次间接访问、要小心内存管理(优先用 unique_ptr)。

一句话:头文件只留门面,实现藏在 cpp 里面。

C++ 最简示例(现代 C++,用 unique_ptr)

头文件 my_class.h(对外给别人 include 的)

cpp 复制代码
#ifndef MY_CLASS_H
#define MY_CLASS_H

#include <memory>
#include <string>

// 对外类
class MyClass
{
public:
    MyClass();
    ~MyClass();

    // 接口
    void setName(const std::string& name);
    std::string getName() const;
    void doWork();

    // 拷贝、移动需要自己声明,因为unique_ptr不可拷贝
    MyClass(MyClass&&) noexcept;
    MyClass& operator=(MyClass&&) noexcept;

private:
    // 前向声明实现类,不写定义!
    struct Impl;
    std::unique_ptr<Impl> pimpl;
};

#endif

✅ 重点:struct Impl 只是前向声明,头文件里看不到里面有什么,没有任何私有成员变量。

源文件 my_class.cpp(实现,不对外)

cpp 复制代码
#include "my_class.h"
#include <iostream>
#include <string>

// 在这里才定义真正的实现类,所有私有成员、依赖都放这里
struct MyClass::Impl
{
    std::string name;
    int internal_data = 0;

    void doInternalLogic()
    {
        internal_data++;
        std::cout << "inner logic, data=" << internal_data << "\n";
    }
};

MyClass::MyClass() : pimpl(std::make_unique<Impl>())
{}

MyClass::~MyClass() = default;

MyClass::MyClass(MyClass&&) noexcept = default;
MyClass& MyClass::operator=(MyClass&&) noexcept = default;

void MyClass::setName(const std::string& name)
{
    pimpl->name = name;
}

std::string MyClass::getName() const
{
    return pimpl->name;
}

void MyClass::doWork()
{
    pimpl->doInternalLogic();
}

使用方 main.cpp

cpp 复制代码
#include "my_class.h"
#include <iostream>

int main()
{
    MyClass obj;
    obj.setName("hello pimpl");
    std::cout << obj.getName() << "\n";
    obj.doWork();
    return 0;
}

关键注意点(踩坑高频)

  1. 析构函数必须在 cpp 里定义(或者 = default 写在 cpp) 如果你把 ~MyClass() = default; 写在 .h 里:编译器在头文件实例化析构时,还不知道 Impl 的完整类型,unique_ptr 析构会编译报错!

    规则:unique_ptr<T> 要求 T 在析构点是完整类型。

  2. 默认拷贝是禁用的 unique_ptr 不能拷贝,所以如果你需要拷贝语义,不能直接 default,要自己在 Impl 里面实现深拷贝:

    cpp 复制代码
    MyClass::MyClass(const MyClass& rhs)
        : pimpl(std::make_unique<Impl>(*rhs.pimpl))
    {}
  3. 对比不使用 PIMPL 的普通写法: 普通写法:所有私有成员、依赖头文件全部暴露在 .h,一旦私有成员改了,所有 include 这个头的文件全部重编译。

相关推荐
星恒随风38 分钟前
MySQL 从零入门(五):MySQL 内置函数详解——日期、字符串、数学函数与实战案例
linux·数据库·笔记·sql·mysql
唐墨1235 小时前
linux kernel 比较数据大小的宏定义
linux·算法
程序开发阿元5 小时前
密码不进工具:一个独立开发者的服务器凭据管理办法
linux·ssh
AR-26710-9 小时前
Linux Day33——定时任务+lvm
linux·运维
墨痕诉清风9 小时前
SSRF漏洞实战记录+OSS桶命令执行+MCP漏洞
linux·服务器·数据库
byte轻骑兵9 小时前
实战 4:BlueZ 源码中添加自定义设备过滤规则
linux·物联网·bluez·电脑蓝牙·嵌入式蓝牙
瓦瓦gogo10 小时前
从一个 TypeScript 函数开始理解 MCP 协议
linux·ubuntu·typescript
鱼听禅11 小时前
Ubuntu安装YOLOv8进行菌落识别训练
linux·python·yolo·ubuntu
测试界的小码农12 小时前
【Linux 命令实战(04):文件内容查看,掌握 cat、less、head、tail】
linux·服务器·less
CodeYp201912 小时前
第13讲:HTTPS 协议原理
linux·https