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 这个头的文件全部重编译。

相关推荐
q27551300422 小时前
AS8132 DP转HDMI 4K60(420)被动式 国产性价比AS方案
linux·算法·硬件架构·dash
刚入门的大一新生2 小时前
Linux-进程间通信2
linux·运维·服务器
电科_银尘3 小时前
【深度学习项目实战·02】Linux服务器环境搭建:从SSH登录到稳定运行项目
linux·服务器·深度学习
运维小斌3 小时前
麒麟 V10 部署 OceanBase:故障速查与验收清单
linux·数据库·oceanbase·麒麟
水饺编程4 小时前
第2章:Linux 基础,在 root 中创建用户账号
linux·c语言·汇编·1024程序员节
叼烟扛炮4 小时前
Linux 学习笔记(四):文件检索与管道重定向
linux·笔记·学习
帷幕落秋5 小时前
Linux 网络基础
linux
高晶5 小时前
ARM 嵌入式 Linux 命令系统 BusyBox 实战指南
linux·设计模式·程序员
祥Sir的SRE笔记6 小时前
CPU 100% 不一定是故障:压测打满、业务饱和、突变打满的判断与排查
linux·面试·性能优化·dba·sre·故障定位·性能排查