当前位置: 首页 > article >正文

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

2024/11/14:

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

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

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

        举个例子:

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设计模式,在这个类上面套一层壳子,将它隐藏起来。

        具体如下面代码所示:

// 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;
};
// 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类上的一层壳,并且成功隐藏了底层较为复杂的实现。

        下列代码用于测试:

// 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类的输出函数:

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设计模式与动态库的匹配性。


http://www.kler.cn/a/395392.html

相关文章:

  • centos7安装Chrome使用selenium-wire
  • 计算机毕业设计Python+大模型中医养生问答系统 知识图谱 医疗大数据 中医可视化 机器学习 深度学习 人工智能 大数据毕业设计
  • nginx proxy_pass中斜杠问题
  • STM32设计学生宿舍监测控制系统
  • 表的数据结构和常见操作
  • 速盾:如何有效防止服务器遭受攻击?
  • 【Pikachu】目录遍历实战
  • 论文解析:计算能力资源的可信共享:利益驱动的异构网络服务提供机制
  • 群控系统服务端开发模式-应用开发-前端角色功能开发
  • 解决Oracle DECODE函数字符串截断问题的深度剖析20241113
  • Ubuntu相关指令
  • 数据结构Python版
  • sqoop import将Oracle数据加载至hive,数据量变少,只能导入一个mapper的数据量
  • 【GPTs】MJ Prompt Creator:轻松生成创意Midjourney提示词
  • 【Git从入门到精通】——Git分支介绍与GitHub相关知识总结
  • Spring Boot与工程认证:计算机课程管理的新纪元
  • Spring Boot框架:电商系统的设计与实现
  • 037 RabbitMQ集群
  • 【Linux】多线程(中)
  • 电子电气架构 --- 基于以太网的电子电气架构概述
  • 大模型在蓝鲸运维体系应用——蓝鲸运维开发智能助手
  • 文心一言 VS 讯飞星火 VS chatgpt (389)-- 算法导论25.1 2题
  • 【Qt聊天室客户端】消息功能--发布程序
  • C++常用的新特性-->day06
  • 多窗口切换——selenium
  • 力扣 平衡二叉树-110