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

news2024/11/16 0:32:18

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.coloradmin.cn/o/2241157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入理解AIGC背后的核心算法:GAN、Transformer与Diffusion Models

深入理解AIGC背后的核心算法&#xff1a;GAN、Transformer与Diffusion Models 前言 随着人工智能技术的发展&#xff0c;AIGC&#xff08;AI Generated Content&#xff0c;人工智能生成内容&#xff09;已经不再是科幻电影中的幻想&#xff0c;而成为了现实生活中的一种新兴力…

LeetCode面试经典150题C++实现,更新中

用C实现下面网址的题目 https://leetcode.cn/problems/merge-sorted-array/?envTypestudy-plan-v2&envIdtop-interview-150 1、数组\字符串 88合并两个有序数组 以下是使用 C 实现合并两个有序数组的代码及测试用例 C代码实现 #include <iostream> #include &l…

python怎么安装numpy

1、在python官网https://pypi.python.org/pypi/numpy中找到安装的python版本对应的numpy版本。 例如&#xff1a; python版本是&#xff1a; 下载的对应numpy版本是&#xff1a; 2、将numpy下载到python的安装目录下的scripts文件夹中&#xff1b; 3、然后在cmd中执行以下命…

js中typeOf无法区分数组对象

[TOC]&#xff08;js中typeOf无法区分数组对象) 前提&#xff1a;很多时候我们在JS中用typeOf来判断值类型&#xff0c;如&#xff1a;typeOf ‘abc’//string ,typeOf 123 //number; 但当判断对象为数组时返回的仍是’object’ 这时候我们可以使用Object.prototype.toString.c…

JavaScript方法修改 input type=file 样式

html中的<input type "file">的样式很难修改&#xff0c;又跟页面风格很不匹配。我就尝试了几种方法&#xff0c;但是不管是用label还是用opacity:0都很麻烦&#xff0c;还老是出问题&#xff0c;所以最后还是用JavaScript来解决。 下面附上代码&#xff1a;…

JS爬虫实战之TikTok_Shop验证码

TikTok_Shop验证码逆向 逆向前准备思路1- 确认接口2- 参数确认3- 获取轨迹参数4- 构建请求5- 结果展示 结语 逆向前准备 首先我们得有TK Shop账号&#xff0c;否则是无法抓取到数据的。拥有账号后&#xff0c;我们直接进入登录。 TikTok Shop 登录页面 思路 逆向步骤一般分为…

MDBook 使用指南

MDBook 是一个灵感来自 Gitbook 的强大工具&#xff0c;专门用于创建电子书和文档。它能够将 Markdown 编写的内容编译成静态网站&#xff0c;非常适合项目文档、教程和书籍的发布。 个人实践过许多文档方案&#xff0c;如 hexo、hugo、WordPress、docsify 和 mdbook 等&#…

力扣 LeetCode 28. 找出字符串中第一个匹配项的下标(Day4:字符串)

解题思路&#xff1a; KMP算法 需要先求得最长相等前后缀&#xff0c;并记录在next数组中&#xff0c;也就是前缀表&#xff0c;前缀表是用来回退的&#xff0c;它记录了模式串与主串(文本串)不匹配的时候&#xff0c;模式串应该从哪里开始重新匹配。 next[ j - 1 ] 记录了 …

海思3403对RTSP进行目标检测

1.概述 主要功能是调过live555 testRTSPClient 简单封装的rtsp客户端库&#xff0c;拉取RTSP流&#xff0c;然后调过3403的VDEC模块进行解码&#xff0c;送个NPU进行目标检测&#xff0c;输出到hdmi&#xff0c;这样保证了开发没有sensor的时候可以识别其它摄像头的视频流&…

Python学习26天

集合 # 定义集合 num {1, 2, 3, 4, 5} print(f"num&#xff1a;{num}\nnum数据类型为&#xff1a;{type(num)}") # 求集合中元素个数 print(f"num中元素个数为&#xff1a;{len(num)}") # 增加集合中的元素 num.add(6) print(num) # {1,2,3,4,5,6} # 删除…

图论-代码随想录刷题记录[JAVA]

文章目录 前言深度优先搜索理论基础所有可达路径岛屿数量岛屿最大面积孤岛的总面积沉默孤岛Floyd 算法dijkstra&#xff08;朴素版&#xff09;最小生成树之primkruskal算法 前言 新手小白记录第一次刷代码随想录 1.自用 抽取精简的解题思路 方便复盘 2.代码尽量多加注释 3.记录…

测试自动化如何和业务流程结合?

测试自动化框架固然重要&#xff0c;但是最终自动化的目的都是为了业务服务的。 那测试自动化如何对业务流程产生积极影响&#xff1f; 业务流程的重要性 测试自动化项目并非孤立存在&#xff0c;其生命周期与被测试的应用程序紧密相关。项目的价值在于被整个开发团队所使用&a…

大模型基础BERT——Transformers的双向编码器表示

大模型基础BERT——Transformers的双向编码器表示 整体概况 BERT&#xff1a;用于语言理解的深度双向Transform的预训练 论文题目&#xff1a;BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding Bidirectional Encoder Representations from…

Leetcode 56-合并区间

以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 //按左边界排序 //startintervals[i][0],endintervals…

【golang-技巧】-线上死锁问题排查-by pprof

1.背景 由于目前项目使用 cgo golang 本地不能debug, 发生死锁问题&#xff0c;程序运行和期待不一致&#xff0c;通过日志排查可以大概率找到 阻塞范围&#xff0c;但是不能找到具体问题在哪里&#xff0c;同时服务器 通过k8s daemonset 部署没有更好的方式暴露端口 获取ppr…

7天用Go从零实现分布式缓存GeeCache(总结)

1. Lru包 1.1 lru算法简要概述 &#xff08;作者&#xff1a;豆豉辣椒炒腊肉/链接&#xff1a;https://juejin.cn/post/6844904049263771662&#xff09; LRU算法全称是最近最少使用算法&#xff08;Least Recently Use&#xff09;&#xff0c;广泛的应用于缓存机制中。当缓…

oracle查询字段类型长度等字段信息

1.查询oracle数据库的字符集 SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER NLS_CHARACTERSET; 2.查询字段长度类型 SELECT * FROM user_tab_columns WHERE table_name user AND COLUMN_NAME SNAME 请确保将user替换为您想要查询的表名。sname为字段名 这里的字…

前端面试笔试(一)

目录 一、数据结构算法等综合篇 1.直接插入排序&#xff0c;有n个元素待排序&#xff0c;则最多进行多少次比较 2.软件测试中评估网络性能的关键指标有哪些 3.哈希查找 4.内存保护 二、代码输出篇 1.promise中throw new Error输出 2.Promise.all 3.this关键字,obj.get…

iOS 18.2 重磅更新:6个大动作

根据外媒报道&#xff0c;iOS 18.2迎来重磅更新&#xff0c;将带来6个大动作&#xff0c;这是一次非常实用的更新。不过要注意的是&#xff0c;其中涉及到AI的功能&#xff0c;国行iPhone 暂时还不可用&#xff0c;只能等审核通过了。 1&#xff0c;Safari下载进度 过去通过S…

HBase理论_HBase架构组件介绍

近来有些空闲时间&#xff0c;正好最近也在开发HBase相关内容&#xff0c;借此整理一下学习和对HBase组件的架构的记录和个人感受&#xff0c;付出了老夫不少心血啊&#xff0c;主要介绍的就是HBase的架构设计以及我的拓展内容。内容如有不当或有其他理解 matirx70163.com HB…