C++ Pimpl 用法

C++开发中使用 Pimpl 的用法可以加速编译速度、避免头文件包含依赖、保持更新后ABI的兼容性。原始的用法是将所有的私有成员变量放在头文件中,这样所有包含该头文件的源文件都需要同步包含一些依赖,Pimpl将其私有成员放到一个类或结构体中,然后提供一个私有指针来进行操作,所有的实现都放在cpp文件中,提供了更好的封装性

1、原始指针方式

将所有成员变量放到一个结构体 ImplData

原始指针的形式
// RawPtr.h 头件
#pragma once
#include <string>

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

    void SetCity(const std::string& cityName);

private:
    struct ImplData;   // 声明一个结构体
    ImplData* m_impl;  // 声明一个该结构体的指针成员
};

// cpp 文件
#include "RawPtr.h"
#include <vector>

using std::string;
using std::vector;

struct WidgetRaw::ImplData
{
    string city;
    int year;
    vector<int> index;
};

WidgetRaw::WidgetRaw():
    m_impl(new ImplData)
{

}

WidgetRaw::~WidgetRaw()
{
    delete m_impl;
    m_impl = nullptr;
}

void WidgetRaw::SetCity(const string& cityName)
{
    m_impl->city = cityName;
}

2、智能指针方式

智能指针
// SmartPtr.h 文件

#include <memory>
#include <string>

class WidgetSmart
{
public:
    WidgetSmart();
    ~WidgetSmart(); // 必须要自定义析构函数,避免编译器自动生成默认析构函数

    void SetCity(const std::string& city);

private:
    struct ImplData;
    std::unique_ptr<ImplData> m_impl; // 使用智能指针来代替原始指针
};
// cpp 文件

#include "SmartPtr.h"
#include <vector>

using std::string;
using std::vector;

struct WidgetSmart::ImplData
{
    string city;
    int year;
    vector<int> index;
};

WidgetSmart::WidgetSmart():
    m_impl(std::make_unique<ImplData>())
{

}

WidgetSmart::~WidgetSmart()
{

}

void WidgetSmart::SetCity(const std::string& city)
{
    m_impl->city = city;
}

【注意】在智能指针实现中,必须要在头文件声明析构函数,在实现中定义析构函数,以避免编译器生成默认析构函数,否则会编译报错。因为如果没有自定义的析构函数,编译器默认生成析构函数时,此时头文件中只有 ImplData的前向声明,是一个不完整类型。编译器在生成默认析构函数时,在类中没有找到ImplData的定义,就会立刻报错 use of undefined type 'WidgetSmart::ImplData' ,虽然在cpp中定义了ImplData,单编译器此时还未解析到这里。如果在头文件中声明自定义的析构函数,然后在cpp中进行定义,就是告诉编译器它的实现在后面,等解析到析构函数处,已经有了ImplData的完整定义。


参考资料:
当使用Pimpl惯用法,请在实现文件中定义特殊成员函数

posted @ 2025-07-02 11:22  Jeffxue  阅读(28)  评论(0)    收藏  举报