跟我学c++中级篇——Pimpl
一、前向声明
前向声明或者前置声明(forward declaration),这个在c++中用得还是比较多的。一般的框架或者库中,经常可以看到在一个类的前面声明了一个类,类似下面这样:
class useclass;
class mycall{...useclass *us;
};
前向声明,就是在应用这个类的某个类或者区域前声明一下,因此,被声明的类,对编译器来说是一个不完全的类型(incomplete type),它只是负责告诉编译器这个类型或者名称是有的,但没有这里(这和extern在这一点上有点类似)实现。所以编译如果遇到只是声明这个类的指针(或引用)的情况下,允许它编译通过。但是,在这种情况下是不允许直接操作这个类的内部一些特征的接口(函数或者变量)。换句话说,不允许定义这个类的对象,而只能受限使用即指针或引用以及用于声明为该类型做为形参或者返回值的函数。
它用在什么场景下?有什么用?举一个例子,如果有两个A和B,他们需要互相操作彼此,比如A向B写一个数,如果这个数达到一个值,就B就回写A一个值。这时候儿怎么办?如此A和B互相包含头文件,就是循环引用(当然,写在一起或者抽象一层,好吧)。这时候儿就需要一方不包含头文件,而使用这种前向声明,只在cpp文件中包含对方的头文件即可。
另外,前向声明可以解耦。还是A和B类,如果A类是一个接口类,但接口类中主要是操作B,那么,如果把B的头文件放出去就可以达到这个目的。但是,这样有几个问题,一个是可能泄露一些不想泄露的东西(特别某些算法里),另外一个,B类如果需要经常改变,那头文件也得老跟着变。这时候儿就可以把B搞成一个指针进行前向声明,具体的操作只在编译单元中进行。
而头文件的减少,好处还是比较多的,一个是降低了头文件include的顺序引起的莫名的问题,另外一个降低了编译时的依赖,提高了编译速度。
二、Pimpl
Pointer to implementation,也就是Pimpl,即通过指针指向实现而不是赤裸裸的把实现暴露出来。在侯捷老师《c++编程规范》和《Effective Modern c++》以及大牛陈硕的《c++工程实践经验谈》中都对使用PIMPL做为一种编译防火墙提高信息的隐藏度进行了分析说明以及各种工程实践的总结。
Pimpl其实就是使用一个私有的指针,来指向具体的需要隐藏的实现(可以简单理解为把原来接口类中的私有或保护成员抽象出来)。而具体的实现则通过外部接口类来操作这个指针来实现。由于在向外暴露的接口类头文件中只能看到这个私有指针的前向声明和指针声明,外部调用人员啥也看不到。它有几个好处:
1、隔离内外,形成一个安全区,即把错误可控的设计在范围内
2、减少二义性的出现。隐藏就意味着外部调用产生二义性的可能性被尽量隔绝
3、前向声明的好处,头文件的依赖减少并带来的编译开销的降低
4、对ABI有更好的兼容性
5、有可能使用延迟加载,提高资源的利用率
需要说明的是,不光c++可以使用这个技巧,C语言同样可以。
同样,有优点就会有缺点:
1、增加了复杂性,毕竟多一层抽象就多一层效率耗减,同时对指针的管理(new/delete)也增加了复杂性
2、需要处理拷贝(要么禁止掉)
3、const脱离了编译器的掌控,这种况下只能通过一些辅助的手段来达到目的
三、例程
下面看一个Pimpl的例子:
//PimplExample.h
#include <memory>
class PimplExample {
public:PimplExample();~PimplExample();int GetA();int GetB(int);int GetC();private:struct Impl;Impl *pimpl_;std::unique_ptr<Impl> ptr_;// std::shared_ptr<Impl> ptr_;
};
//打开注释,自己试试?
//PimplExample.cpp
#include <list>
#include <string>
struct PimplExample::Impl {int IGetA();int IGetB(int i) { return 0; };int d;std::list<int> l;
};PimplExample::PimplExample() : pimpl_(new Impl()) {}PimplExample::~PimplExample() { delete pimpl_; }int PimplExample::GetA()
{pimpl_->IGetA();
return 0;
}
int PimplExample::GetB(int i)
{pimpl_->IGetB(i);return 0;
}int PimplExample::Impl::IGetA()
{std::cout << "test" << std::endl;return 0;
}
//main.cpp
#include "PimplExample.h"
#include <iostream>
int main() {PimplExample mt;return 0;
}
这里面有一个小细节,如果把指针换成智能指针,试着用shared_ptr和unique_ptr来完成上面的代码,看看有什么问题没有?在实践中发现问题,解决问题,才是提升水平的一个重要手段。换成智能指针时,要把显示的析构函数注释掉,看看两种指针都会有啥现象。
如果想把程序写得更好一些,可以看pimpl类进一步封装成一个单独的类到文件中去,这样就和实际的工程应用相近了。
说Pimpl,其实和前向声明是密不可分的。可以理解为Pimpl是前向声明的一个应用场景(前向声明是Pimpl的泛型)。理解了一个技术的本质,就可以更好的应用这个技术并且屏蔽掉这个技术的副作用。仍然是举这个前向声明或者Pimpl的例子中,不是说Pimpl就包打一切,它其实是增加了复杂性,所以在非接口中,使用它就不一定是好的选择。另外,不把整个应用的层次搞清楚,就无法确定这个Pimpl应用在哪一层接口上更好。
即使如此,在实际的应用场景中,对于一些需要暴露的情况下,不一定非得把所有的成员都抽象到Pimpl的类中,包括处理虚拟函数也是如此。因此,到底如何更好的使用Pimpl需要根据实际情况来确定,不能简单的邯郸学步,有样学样。
四、总结
其实多读书,多实践对学计算机的人来说真得非常重要。很多人只看书,很少实践或者干脆反过来,结果就是进步太慢并且固步自封。同样,多读书,指的是多读精品的书籍而不是什么样的书都读(当然读优秀的代码也可以看成一种读书)。国内的书籍缺点往往是走两个极端,一个是学院派,只讲道理,实践很少或者干脆没有;或者是很多一线的人员写的书籍倾向于实战,理论不足。特别是理论和实践相结合的书籍更是少之又少,这也是往往推荐初学者去学习国外经典的原因。
这其实和国内的环境很有关系,在整个计算机的产业链上,国内仍然处于应用层,偶有底层建设也大多以国外开源为基础,完全原生少之又少。反倒是为某个语言优秀与否吵个沸反盈天,实属不智。这也是大环境使然,随着技术的发展,也许过一些年,这些东西就会补上来,未为可知。
Pimpl做为老生常谈,已经分析过几次了,这里再次补一篇!
相关文章:
跟我学c++中级篇——Pimpl
一、前向声明 前向声明或者前置声明(forward declaration),这个在c中用得还是比较多的。一般的框架或者库中,经常可以看到在一个类的前面声明了一个类,类似下面这样: class useclass; class mycall{...useclass *us; };前向声明…...
[补题记录] Atcoder Beginner Contest 295(E)
URL:https://atcoder.jp/contests/abc295 目录 E Problem/题意 Thought/思路 Code/代码 E Problem/题意 给定长度为 N 的数组 A。进行如下操作: 若 Ai 0,将 Ai 等概率地变为 1 ~ M 中的任意一个数;对 A 排序; …...

解决git在window11操作很慢,占用很大cpu的问题
【git在window11操作很慢,占用很大cpu,最后也执行失败】 在谷歌输入:git very slow in window 11。通过下面链接终于找到了解决方案: https://www.reddit.com/r/vscode/comments/sulebx/slow_git_in_wsl_after_updating_to_window…...

C++智能指针(二)——weak_ptr初探
文章目录 1. shared_ptr 存在的问题2. 使用weak_ptr2.1 初始化 weak_ptr2.2 访问数据 3. 附录4. 参考文献 1. shared_ptr 存在的问题 与 shared_ptr 的引入要解决普通指针存在的一些问题一样,weak_ptr 的引入,也是因为 shared_ptr 本身在某些情况下&…...
540 - Team Queue (UVA)
题目链接如下: Online Judge 对比刘汝佳的代码,我没有用queue来排整个队伍,因为那样的话遍历整个队伍太麻烦,vector比较方便。但vector删除元素比较耗时,所以就不删了,仅仅用pivot来指代目前队伍的开始。…...

投资组合之如何估值
文章目录 如何估值一、PE估值法1、PE估值法的定义2、参考标准(1)常规标准:25倍合理市盈率。(2)同行业对比。(3)跟历史市盈率相比。 3、PE估值法的适用范围4、PE估值法的优势5、PE估值法的劣势&a…...

2024届通信工程保研经验分享(预推免入营即offer)
2024届通信工程保研经验分享(预推免入营即offer) BackGround夏令营情况:预推免情况: BackGround 本科院校:末九 专业:通信工程 rank:3/123(预推免绩点排名)࿰…...
L2-025 分而治之 - java
L2-025 分而治之 时间限制 600 ms 内存限制 64 MB 题目描述: 分而治之,各个击破是兵家常用的策略之一。在战争中,我们希望首先攻下敌方的部分城市,使其剩余的城市变成孤立无援,然后再分头各个击破。为此参谋部提供了若…...

Python+高光谱数据预处理-机器学习-深度学习-图像分类-参数回归
涵盖高光谱遥感数据处理的基础、python开发基础、机器学习和应用实践。重点解释高光谱数据处理所涉及的基本概念和理论,旨在帮助学员深入理解科学原理。结合Python编程工具,专注于解决高光谱数据读取、数据预处理、高光谱数据机器学习等技术难题…...
免费 AI 编程助手 Amazon CodeWhisperer 体验
文章作者:文章作者:米菲爸爸 2022 年 6 月 23 亚马逊云科技就已经推出了 Amazon CodeWhisperer(预览版)。经过不到一年的测试和 AIGC的飓风在 2023 年 4 月 18 日实时 AI 编程助手 Amazon CodeWhisperer正式可用 Amazon CodeWhis…...

【Linux】从零开始学习Linux基本指令(一)
🚩纸上得来终觉浅, 绝知此事要躬行。 🌟主页:June-Frost 🚀专栏:Linux入门 🔥该文章主要了解Linux操作系统下的基本指令。 目录: ⌛️指令的理解⏳目录和文件的理解⏳一些常见指令✉…...

Java GC 算法
一、概述 理解Java虚拟机垃圾回收机制的底层原理,是成为一个高级Java开发者的基本功。本文从底层的垃圾回收算法开始,着重去阐释不同垃圾回收器在算法设计和实现时的一些技术细节,去探索「why」这一部分,通过对比不同的垃圾回收算…...
vue3 v-html中使用v-viewer
安装:npm install v-viewernext 在main.js中配置 import “viewerjs/dist/viewer.css”; import Viewer from “v-viewer”; app.use(Viewer, { Options: { inline: true, //默认值:false。启用内联模式。 button: true, //在查看器的右上角显示按钮。 …...

Leetcode算法解析——查找总价格为目标值的两个商品
1. 题目链接:LCR 179. 查找总价格为目标值的两个商品 2. 题目描述: 商品价格按照升序记录于数组 price。请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况,返回任一结果即可。 示例 1: 输入:price …...
unity游戏开发引擎unity3D开发
Unity(也被称为Unity3D)是一款强大的跨平台游戏引擎,用于开发2D和3D游戏,以及其他交互式应用程序。以下是Unity游戏开发的一般步骤: 安装和设置Unity: 首先,您需要下载并安装Unity。确保选择适…...
iptables
目录 iptables 匹配规则:由上到下依次匹配,一旦匹配不再匹配 参数 知识点 REJECT与DROP REJECT与DROP的区别 当使用的时REJECT时,客户端访问迅速返回的值是拒绝连接 当使用的是DROP时,返回的时连接超时 REJECT与drop适用…...

竞赛 深度学习LSTM新冠数据预测
文章目录 0 前言1 课题简介2 预测算法2.1 Logistic回归模型2.2 基于动力学SEIR模型改进的SEITR模型2.3 LSTM神经网络模型 3 预测效果3.1 Logistic回归模型3.2 SEITR模型3.3 LSTM神经网络模型 4 结论5 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 …...
Spark入门
目录 Spark入门: 概述历史概述SparkCore:RDDSparkSQL:SparkStreamingSpark内核调优 Spark概述 回顾: Hadoop HDFS存储 MR分析计算 YARN调度 Hadoop的MR计算中的shuffle需要落盘,速度不够快。 Spark是一种基于内存的分析计算引擎。 历史…...

react–antd 实现TreeSelect树形选择组件,实现点开一层调一次接口
效果图: 注意: 当选择“否”,开始调接口,不要把点击调接口写在TreeSelect组件上,这样会导致问题出现,没有层级了 部分代码:...

android 固定进度环形刷新效果
android 固定进度无限旋转的环形效果 效果图 效果视频: Record_2023-10-13-17-17-19[1] Activity 中使用 val rotation: ObjectAnimator ObjectAnimator.ofFloat(progressBar, "rotation", 0f, 360f) rotation.duration 000 // 旋转持续时间为2秒 rot…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...

FTPS、HTTPS、SMTPS以及WebSockets over TLS的概念及其应用场景
一、什么是FTPS? FTPS,英文全称File Transfer Protocol with support for Transport Layer Security (SSL/TLS),安全文件传输协议,是一种对常用的文件传输协议(FTP)添加传输层安全(TLS)和安全套接层(SSL)加密协议支持的扩展协议。…...

NoSQL——Redis配置与优化
目录 关系型&非关系型数据库 一、核心原理对比 二、核心特性对比 三、关键区别剖析 四、典型产品示例 总结 Redis Redis核心原理 核心特性 技术意义 配置文件解析 1. 基础配置 2. 持久化配置 3. 内存管理 4. 高可用配置 5. 性能调优 6.…...

Redis知识体系
1. 概述 本文总结了Redis基本的核心知识体系,在学习Redis的过程中,可以将其作为学习框架,以此更好的从整体的角度去理解和学习Redis的内容和设计思想。同时知识框架带来的好处是可以帮助我们更好的进行记忆,在大脑中形成相应的知识…...
Codeforces Educational 179(ABCDE)
前言 byd这组题纯靠感觉是吧…^_^ b题赛时举了无数个例子都没想明白,然后一直卡到结束,后面题都没看到,结果补题的时候c题d题直接秒了…-_-|| A. Energy Crystals #include <bits/stdc.h> using namespace std;typedef long long …...