【Qt】Qt 隐式共享

news2024/9/24 21:26:30

文章目录

    • 一、导读
    • 二、隐式共享简介
    • 三、源码角度分析隐式共享
    • 四、隐式共享在开发中的使用
    • 五、隐式共享迭代器问题
    • 六、隐式共享类和线程

一、导读

在实际开发中,Qt中很多类可以直接作为函数参数传递,这样做是为了什么?其背后的实现机制又是什么?这些都归功于隐式共享,本文基于Qt 5.15源码,来聊聊隐式共享!

二、隐式共享简介

​ Qt中的许多C++类使用隐式数据共享来提高资源使用并减少数据复制。当这些类作为参数传递时,因为只传递一个指向数据的指针,并且只有当函数写入数据时数据才会被复制,即copy -on-write,隐式共享类是安全、高效的。

​ 共享类由一个指向包含引用计数数据的共享数据块的指针组成。

​ 当创建共享对象时,它将引用计数设置为1。每当有新对象引用共享数据时,引用计数就递增,当对象解引用共享数据时,引用计数就递减,当引用计数变为零时,将删除共享数据。

​ 在处理共享对象时,有两种方法复制对象。也就是经常谈到的:深度拷贝浅拷贝。深度拷贝意味着复制一个对象,浅拷贝是一个引用拷贝,也就是一个指向共享数据块的指针。站在内存和CPU角度,执行一个深度拷贝可能是昂贵的操作,执行浅拷贝则非常快,因为浅拷贝只涉及设置指针和增加引用计数。

注意:隐式共享对象的对象赋值(operator=())是使用浅拷贝实现的。

​ 隐式共享的优点是:

  • (1)程序不需要进行不必要的数据复制操作,从而减少内存的使用和多次执行数据复制操作。
  • (2)可以很容易地被赋值。
  • (3)可以作为函数参数传递,并从函数中返回。

三、源码角度分析隐式共享

隐式共享会自动将对象从共享块中分离出来,如果对象即将改变并且引用计数大于1,(这通常被称为写时复制或值语义。)

隐式共享类可以控制其内部数据,在任何修改其数据的成员函数中,它都会在修改数据之前自动分离。(但是,需注意容器迭代器的特殊情况,后文将说明这一点!)

此处以QPen这个隐式共享类为例,从源码角度分析QPen类是如何从更改内部数据的成员函数中分离共享数据的。在Qt5.15源码中用于描述QPen的文件为qpen_p.h、qpen.cpp、qpen.h三个文件,位于源码路径(/qtbase/src/gui/painting目录)下。在QPen类定义中有一个detach()

实现如下:

detach()用于从共享pen数据中分离,以确保该pen只有一个引用数据,如果多个pen共享公共数据,这支pen将取消对数据的引用并获得数据的副本;如果只有一个则返回,什么也不做。上述代码中,QPenData实则是QPenPrivate的类型别名,用于描述QPen的数据,定义如下(位于qpen_p.h文件中):

上述代码分析了detach()函数,下文以QPen的一个成员函数setStyle(Qt::PenStyle style)来描述,该函数实现如下:

从上述图片所示,在setStyle()函数中,会使用detach()从公共数据中分离,然后在设置style成员。

综上,如果Qt提供的类支持隐式共享,那么其源码内部实现都有对应的数据管理机制,实现写时复制。

四、隐式共享在开发中的使用

上述第二节描述了隐式共享的QPen类如何从更改内部数据的成员函数中分离共享数据。可简化为下述代码片段:

void QPen::setStyle(Qt::PenStyle s)
{
    detach();           // 从公共数据中分离
    d->style = s;   	// 设置style成员
}

void QPen::detach()
{
    if (d->ref != 1) {
        ...             // 执行深度拷贝
    }
}

所以,在开发中如果更改了对象,类将自动与公共数据分离,甚至不会注意到这些对象是共享的。因此,可以将它们的单独实例视为单独的对象,它们始终作为独立的对象。但在有些情况下可以共享数据,因此可以将这些类的实例作为参数按值传递给函数,而不必考虑复制开销。

例如下列代码:

QPixmap p1, p2;
p1.load("image.bmp");
p2 = p1;                        // p1 和 p2 共享数据

QPainter paint;
paint.begin(&p2);               // 将p2从p1中分离出来
paint.drawText(0,50, "iriczhao");
paint.end();

注:在使用stl风格的迭代器时,复制隐式共享容器(QMapQList等)需要特别注意。

Qt官方提供了一个隐式共享类的表格,通过查看该表可获得Qt中哪些类是隐式共享类。

URL:https://doc.qt.io/qt-6/implicit-sharing.html

五、隐式共享迭代器问题

对于stl风格的迭代器,在使用隐式共享类时应格外注意。因为当迭代器在容器上激活时,应该避免复制容器。也就是迭代器指向一个内部结构,如果复制一个容器,此时应特别注意迭代器。例如以下代码片段:

QList<int> a, b;
a.resize(100000); // 创建一个大列表,里面填满0。

QList<int>::iterator i = a.begin();

/*-------------------------------------------------------------*/

// 使用迭代器i的错误方法:
b = a;
/*
    此时我们应该注意迭代器i,因为它将指向共享数据
    如果我们执行*i = 4,那么我们将改变共享实例(两个向量)
    其行为不同于STL容器。在Qt中不能这样做。
*/

/*-------------------------------------------------------------*/

a[0] = 5;
/*
    容器a现在与共享数据分离,
    尽管i是容器a的迭代器,但是它现在作为容器b的迭代器工作。
    这里的情况是(*i) == 0。
*/

b.clear(); // 现在迭代器i完全无效了。

int j = *i; //此时会出现未定义的行为!
/*
    来自b(i所指向的)的数据不见了。
    这可以用STL容器(和(*i) == 5)定义,
    但是这时候使用QList,可能会崩溃。
*/

​ 总而言之:当迭代器在容器上激活时,应该避免复制容器,所有的Qt容器类都应该注意这一点。

六、隐式共享类和线程

在Qt中,对它的许多值类使用了隐式共享进行了优化,尤其是QImageQString从Qt 4开始,隐式共享类可以安全地跨线程复制。这些值类是完全可重入的。

一般情况下,都认为隐式共享和多线程是不兼容的概念,因为引用计数通常不允许这样做。然而,Qt使用原子引用计数来确保共享数据的完整性,避免了引用计数器的潜在损坏。

​ 但是需要注意原子引用计数不能保证线程安全性。在线程之间共享隐式共享类的实例时,应该适当的加锁进行锁定。这一点,与所有重入类(无论是否共享)相同。原子引用计数确实保证了一个线程在其自身、隐式共享类的本地实例上工作是安全的,所以,在开发中可以使用信号和槽函数机制在不同线程之间传递数据,因为这可以在不需要显式锁定的情况下完成。

​ 总而言之,Qt 中的隐式共享类实际上是隐式共享的。即使在多线程应用程序中,也可以安全地使用它们,与普通的、非共享的、可重入的基于值的类一样。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/340647.html

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

相关文章

进程,线程

进程是操作系统分配资源的基本单位&#xff0c;线程是CPU调度的基本单位。 PCB&#xff1a;进程控制块&#xff0c;操作系统描述程序的运行状态&#xff0c;通过结构体task,struct{…}&#xff0c;统称为PCB&#xff08;process control block&#xff09;。是进程管理和控制的…

#电子电气架构——Vector工具常见问题解决三板斧

我是穿拖鞋的汉子,魔都中一位坚持长期主义的工科男。 今天在与母亲聊天时,得到老家隔壁邻居一位大姐年初去世的消息,挺让自己感到伤感!岁月如流水,想抓都抓不住。想起平时自己加班的那个程度,可能后续也要自己注意身体啦。 老规矩,分享一段喜欢的文字,避免自己成为高知…

千锋教育嵌入式物联网教程之系统编程篇学习-03

目录 进程的终止 exit函数 _exit函数 进程退出清理 进程间的替换 进程间通信 常见通信机制 进程间通信的实质 信号 产生信号的方式 信号的默认处理方式 进程对信号的处理方式 kill函数 进程的终止 使用exit函数对进程进行终止&#xff0c;而return只是结束函数&a…

电子技术——共模抑制

电子技术——共模抑制 我们在之前学习过&#xff0c;无论是MOS还是BJT的差分输入对&#xff0c;共模信号并不会改变漏极电流的大小&#xff0c;因此我们说差分输入对共模信号无响应。但是实际上由于各种客观非理想因素&#xff0c;例如电流源有限阻抗等&#xff0c;此时共模是影…

LINUX提权入门手册

前言 发点存货 LINUX权限简介 在学习提权之前我们先了解一下linux里面的权限我们使用命令: ls -al即可查看列出文件所属的权限&#xff1a; 文件头前面都有一段类似的字符&#xff0c;下面我们仔细分析一下里面符号分别代表什么。 -rw-r--r-- 1 root root 第一个符号-的…

现代 cmake (cmake 3.x) 操作大全

cmake 是一个跨平台编译工具&#xff0c;它面向各种平台提供适配的编译系统配置文件&#xff0c;进而调用这些编译系统完成编译工作。cmake 进入3.x 版本&#xff0c;指令大量更新&#xff0c;一些老的指令开始被新的指令集替代&#xff0c;并加入了一些更加高效的指令/参数。本…

MongoDB--》MongoDB数据库以及可视化工具的安装与使用—保姆级教程

目录 数据库简介 MongoDB数据库的安装 MongoDB数据库的启动 MongoDB数据库环境变量的配置 MongoDB图形化管理工具 数据库简介 在使用MongoDB数据库之前&#xff0c;我们应该要知道我们使用它的原因&#xff1a; 在数据库当中&#xff0c;有常见的三高需求&#xff1a; Hi…

如何手写一个springboot starter?

本文主要分享了如何手写一个spring starter&#xff0c;把你的代码作为jar包进行开源。命名规则&#xff08;不要使用spring-boot开头&#xff0c;以避免将来spring-boot官方使用你的starter而重名&#xff09;官方命名格式为&#xff1a;spring-boot-starter-xxx非官方命名格式…

ucos-ii 的任务调度原理和实现

ucosii 任务调度和原理1、ucos-ii 任务创建与任务调度 1.1、任务的创建 当你调用 OSTaskCreate( ) 进行任务的创建的时候&#xff0c;会初始化任务的堆栈、保存cpu的寄存器、创建任务的控制块&#xff08;OS_TCB&#xff09;等的操作&#xff1b; if (OSTCBPrioTbl[prio] (OS_…

Python中如何书写文件路径

当程序运行时&#xff0c;变量是保存数据的好方法&#xff0c;但变量、序列以及对象中存储的数据是暂时的&#xff0c;程序结束后就会丢失&#xff0c;如果希望程序结束后数据仍然保持&#xff0c;就需要将数据保存到文件中。Python 提供了内置的文件对象&#xff0c;以及对文件…

数据库 delete 表数据后,磁盘空间为什么还是被一直占用?

插&#xff1a; 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家一起学习鸭~~~ 最近有个上位机获取下位机上报数据的项目&#xff0c…

计算机网络6:Http协议

目录HTTP1.基本概念1.1.1 URI2. 请求和响应报文2.1.请求报文2.2.响应报文3.HTTP报文实现细节3.1响应头主要字段3.2HTTP状态码3.3 HTTP方法3.3.1 GET方法3.3.2 HEAD3.3.3 POST3.3.4 PUT3.3.5 PATCH3.3.6 DELETE3.3.7 OPTIONS3.3.8 CONNECT3.4 HTTP首部&#xff08;头部&#xff…

【MT7628】开发环境搭建-Fedora12安装之后无法上网问题解决

1.按照如下图所示,打开Network Connections 2.点击Network Connections,弹出如下界面

面向对象程序(C++)设计基础

一、类&对象C 在 C 语言的基础上增加了面向对象编程&#xff0c;C 支持面向对象程序设计。类是 C 的核心特性&#xff0c;通常被称为用户定义的类型。类提供了对象的蓝图&#xff0c;所以基本上&#xff0c;对象是根据类来创建的。声明类的对象&#xff0c;就像声明基本类型…

面试题(二十二)消息队列与搜索引擎

2. 消息队列 2.1 MQ有什么用&#xff1f; 参考答案 消息队列有很多使用场景&#xff0c;比较常见的有3个&#xff1a;解耦、异步、削峰。 解耦&#xff1a;传统的软件开发模式&#xff0c;各个模块之间相互调用&#xff0c;数据共享&#xff0c;每个模块都要时刻关注其他模…

Grafana 系列文章(十五):Exemplars

Exemplars 简介 Exemplar 是用一个特定的 trace&#xff0c;代表在给定时间间隔内的度量。Metrics 擅长给你一个系统的综合视图&#xff0c;而 traces 给你一个单一请求的细粒度视图&#xff1b;Exemplar 是连接这两者的一种方式。 假设你的公司网站正经历着流量的激增。虽然…

ansible的模块详解

ansible 的概述 什么是ansible Ansible是一款为类Unix系统开发的自由开源的配置和自动化工具。 它用Python写成&#xff0c;类似于saltstack和Puppet&#xff0c;但是有一个不同和优点是我们不需要在节点中安装任何客户端。 它使用SSH来和节点进行通信。Ansible基于 Python…

Redux 源码分析

Redux 目录结构 redux ├─ .babelrc.js ├─ .editorconfig ├─ .gitignore …

列线图工具_Nomogram

定义 列线图是一种相对传统的分析方法&#xff0c;用于展示自变量和因变量的线性关系&#xff0c;及其特征的重要程度。 现在用SHAP&#xff0c;和机器学习库中的 Feature importance 工具可以实现类似甚至更好效果。不过很多传统的研究领域比较认这种方法。 列线图工具建立在…

什么是相机标定

1. 相机标定的定义及作用 相机标定是指借助标定板来计算单个或多个相机的内参、外参和镜头畸变参数。 作用&#xff1a; 将畸变的图像恢复为正常的图像&#xff0c;为后续进行拼接、SLAM等奠定基础。 多相机标定可以将所有相机输出变换到同一个坐标系。 相机标定是三维视觉…