跟我学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…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
