C++基础:Pimpl设计模式的实现

2024/11/14:

在实现C++17的Any类时偶然接触到了嵌套类的实现方法以及Pimpl设计模式,遂记录。

PIMPL ( Private Implementation 或 Pointer to Implementation )是通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏。

通俗点来说,就是因为各种各样的原因(如:你不愿意让别人看到你的具体实现,你希望让头文件看起来更清爽)需要将底层的实现封装起来,只将API暴露出来。

举个例子:

cpp 复制代码
class Line
{
public:
    Line(int x1, int y1, int x2, int y2)
        : _p1(x1, y1)
        , _p2(x2, y2)
    {}
    void showLine()
    {
        std::cout << "Line:" << std::endl;
        _p1.showPoint();
        _p2.showPoint();
    }
private:
    // 嵌套类放入private,专门为line服务
    class Point
    {
    public:
        ~Point() = default;
        Point(int a, int b)
                : _x(a)
                , _y(b)
        {}
        void showPoint() const
        {
            std::cout << "Point:" << std::endl;
            std::cout << _x << " " << _y << std::endl;
        }
    private:
        int _x, _y;

    };
    Point _p1, _p2;
};

Line类对于那些只关注函数功能的用户来说可能会带来一定阅读成本。所以我们使用Pimpl设计模式,在这个类上面套一层壳子,将它隐藏起来。

具体如下面代码所示:

cpp 复制代码
// Line.h
#ifndef LINE_H
#define LINE_H
#include <iostream>
#endif
class OutLine
{
public:
    OutLine(int x1, int y1, int x2, int y2);
    ~OutLine();
    void showOutLine();
private:
    // 嵌套类的前向声明
    class Line;
    Line * _Lineptr;
};
cpp 复制代码
// Line.cpp
#include "Line.h"
class OutLine::Line
{
public:
    Line(int x1, int y1, int x2, int y2)
            : _p1(x1, y1)
            , _p2(x2, y2)
    {}
    void showLine()
    {
        std::cout << "Line:" << std::endl;
        _p1.showPoint();
        _p2.showPoint();
    }
private:
    // 嵌套类放入private,专门为line服务
    class Point
    {
    public:
        ~Point() = default;
        Point(int a, int b)
                : _x(a)
                , _y(b)
        {}
        void showPoint() const
        {
            std::cout << "Point:" << std::endl;
            std::cout << _x << " " << _y << std::endl;
        }
    private:
        int _x, _y;

    };
    Point _p1, _p2;
};

OutLine::OutLine(int x1, int y1, int x2, int y2)
: _Lineptr(new Line(x1, y1, x2, y2))
{
    std::cout << "create outLine" << std::endl;
}

OutLine::~OutLine()
{
    std::cout << "delete outLine" << std::endl;
    delete _Lineptr;
    _Lineptr = nullptr;
}

void OutLine::showOutLine() {
    if (_Lineptr) {
        _Lineptr->showLine();
    }
}

可以看到,在Line.h文件中,我们使用了OutLine类作为套在Line类上的一层壳,并且成功隐藏了底层较为复杂的实现。

下列代码用于测试:

cpp 复制代码
// main.cpp
int main()
{
    OutLine l(1, 4, 2, 3);
    l.showOutLine();
}

Pimpl设计模式主要用于将代码打包成头文件和库交给第三方使用。显而易见的,在类上再套一个类增加了一点点开销,但是相对于好处而言,这点代价是可以接受的。

除了信息的隐藏,还有几点好处:

1、只要头文件不变,可以实现对已实现的函数(参数列表不能变)功能的修改和优化。在修改之后,只需要替换动态库而不需要替换头文件。即可以实现库的平滑升级

这一点是显而易见的,比如,当我们运行上面的测试代码时,会有以下输出:

create outLine

Line:

Point:

1 4

Point:

2 3

delete outLine

可能我们觉得输出左对齐不是很美观,想美化一下输出,所以我们在Line.cpp文件中改一下Point类的输出函数:

cpp 复制代码
void showPoint() const
{
      std::cout << "\tPoint: " <<  _x << " " << _y << std::endl;
}

再运行一下测试代码,得到输出:

create outLine

Line:

Point: 1 4

Point: 2 3

delete outLine

在这个修改过程中,并没有改动Line.h头文件。

2、Pimpl最重要的功能:编译防火墙

这一点其实跟上一点是相关联的,见下图:

reference c++库文件头文件链接原理(全)

共享库(动态库)的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此生成的可执行程序代码体积较小。

有些修改不会使头文件发生变化,只需要替换动态库(动态库是由cpp文件生成的),那么整个项目就不需要重新编译一遍。可以说这种设计模式完全发挥了动态库的优势。

可能有些人对这个编译的时间开销没什么概念,这里举个不怎么恰当的例子:有些游戏只是修了个bug,可能就要让整个游戏重新下载。而注意这方面优化的游戏,可能下个几mb的补丁就结束了。

前面的思路可能有点乱,这里总结一下采用Pimpl模式的优势与动态库的关系:

当动态库更新时,通常情况下不需要重新编译可执行文件。因为在编译生成可执行文件时,链接器并不会将动态库的代码直接复制到可执行文件中,而只是记录了对该动态库的引用。程序在执行时会根据这些引用加载相应的动态库。如果该动态库已被加载,程序则不会重复加载,从而节省内存资源。

然而,如果动态库的接口发生变化(例如函数的参数类型或返回值类型发生改变,或者函数被删除或重命名),那么可能需要重新编译可执行文件。在这种情况下,原有的可执行文件将无法正确调用动态库中的函数,可能会导致错误或异常。因此,Pimpl设计模式的编译防火墙优势就是建立在不去动这些接口的基础上的,这就要求项目有一个良好的设计思路,以尽量减少后期对接口的修改。

可见Pimpl设计模式与动态库的匹配性。

相关推荐
吾与谁归in2 小时前
【C#设计模式(4)——构建者模式(Builder Pattern)】
设计模式·c#·建造者模式
shinelord明3 小时前
【再谈设计模式】建造者模式~对象构建的指挥家
开发语言·数据结构·设计模式
黑不拉几的小白兔3 小时前
PTA部分题目C++重练
开发语言·c++·算法
写bug的小屁孩3 小时前
websocket身份验证
开发语言·网络·c++·qt·websocket·网络协议·qt6.3
chordful3 小时前
Leetcode热题100-32 最长有效括号
c++·算法·leetcode·动态规划
材料苦逼不会梦到计算机白富美3 小时前
线性DP 区间DP C++
开发语言·c++·动态规划
ahadee4 小时前
蓝桥杯每日真题 - 第12天
c++·vscode·算法·蓝桥杯
vortex54 小时前
解决 VSCode 中 C/C++ 编码乱码问题的两种方法
c语言·c++·vscode
醉颜凉6 小时前
【NOIP提高组】潜伏者
java·c语言·开发语言·c++·算法