跟我学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…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...
Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...
