0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于Qt 5.15源码来聊聊隐式共享

嵌入式小生 来源:嵌入式小生 2023-02-12 16:52 次阅读

一、导读

在实际开发中,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():

463b7818-aaae-11ed-bfe3-dac502259ad0.png

实现如下:

46767288-aaae-11ed-bfe3-dac502259ad0.png

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

46a27798-aaae-11ed-bfe3-dac502259ad0.png46bc3426-aaae-11ed-bfe3-dac502259ad0.png

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

4723201e-aaae-11ed-bfe3-dac502259ad0.png

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

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

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

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

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

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

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

例如下列代码:

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

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

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

五、隐式共享迭代器问题

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

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

QList::iteratori=a.begin();

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

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

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

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

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

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

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

六、隐式共享类和线程

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

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

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

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






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10816

    浏览量

    210943
  • C++语言
    +关注

    关注

    0

    文章

    147

    浏览量

    6957
  • 迭代器
    +关注

    关注

    0

    文章

    43

    浏览量

    4300

原文标题:懂Qt,隐式共享都知道吗?

文章出处:【微信号:嵌入式小生,微信公众号:嵌入式小生】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    共享三年嵌入项目资料(源码+实物图+原创)(申精帖)!

    . 医学图像系统(只支持大部分的CT,DR,CR)十二STM32F103VBT6超屏解决OV7660高速数据同步读取十三.DIY的wav播放器(有图有源码)十四. DIY的STM32驱动OV7660
    发表于 04-13 15:25

    手动编译QT源码生成qmake

    交叉编译QT4.8.7源码生成qmake工具TQ-i.MX6UL使用的QT版本是QT4.8.7,板卡出厂前附带的开发资料中,天嵌官方没有为开发者编译生成qmake工具。因此,为了后续进
    发表于 11-05 08:20

    qt源码库在树莓派中的部署方法

    接安装qt。本文介绍最普遍的部署方式,就是在pc端的ubuntu中交叉编译库源码,然后上传编译好的库到树莓派,最后配置qt creator交叉编译的kit实现在pc端编译自码程序,上
    发表于 12-24 06:44

    请问QT怎么实现源码编译?

    你好,QT怎么实现源码编译,
    发表于 12-31 07:49

    qt源码编译安装遇到的问题

    QT源码make通过后执行make install时报错。
    发表于 12-09 15:56

    推荐使用QT5.14或者QT5.15版本 不要急着升级到QT6

      推荐使用QT5.14或者QT5.15版本,不建议升级最新版的QT6,很多你要的功能没了,特别是开发上位机需要的模块没了。 Qt 6.0 中已移除的模块,以下是不在
    的头像 发表于 01-26 16:37 4.7w次阅读

    嵌入Linux的QT版本,嵌入Linux版本Qt5.4快速部署

    关键词:摘要:Qt是一个领先的跨平台应用和UI 开发框架(Framework),使用标准C++,适用于桌面,嵌入和移动平台。本文着重就利用Boot to Qt软件包实现
    发表于 11-01 17:20 0次下载
    嵌入<b class='flag-5'>式</b>Linux的<b class='flag-5'>QT</b>版本,嵌入<b class='flag-5'>式</b>Linux版本<b class='flag-5'>Qt</b>5.4快速部署

    嵌入linux安装qt,嵌入Linux版本Qt5.4快速部署

    摘要:Qt是一个领先的跨平台应用和UI 开发框架(Framework),使用标准C++,适用于桌面,嵌入和移动平台。本文着重就利用Boot to Qt 软件包实现
    发表于 11-02 10:51 0次下载
    嵌入<b class='flag-5'>式</b>linux安装<b class='flag-5'>qt</b>,嵌入<b class='flag-5'>式</b>Linux版本<b class='flag-5'>Qt</b>5.4快速部署

    嵌入Linux开发环境搭建-(6)交叉编译QT4.8.7源码生成qmake工具

    交叉编译QT4.8.7源码生成qmake工具TQ-i.MX6UL使用的QT版本是QT4.8.7,板卡出厂前附带的开发资料中,天嵌官方没有为开发者编译生成qmake工具。因此,为了后续进
    发表于 11-02 13:21 3次下载
    嵌入<b class='flag-5'>式</b>Linux开发环境搭建-(6)交叉编译<b class='flag-5'>QT</b>4.8.7<b class='flag-5'>源码</b>生成qmake工具

    怪兽充电宝 共享充电宝源码

    介绍:怪兽充电宝源码共享充电宝源码,怪兽充电是一款全新的智能共享充电宝产品,受到广大用户的喜爱,也便利了人们的生活。网盘下载地址:http://kekewl.cc/rdeOykY31r
    发表于 01-07 09:36 32次下载
    怪兽充电宝 <b class='flag-5'>共享</b>充电宝<b class='flag-5'>源码</b>

    Qt ECG Monitor Qt嵌入床旁心电监护仪项目源码

    Qt ECG Monitor是由Qt-UI开发和维护的嵌入床旁心电监护仪界面项目。项目提供C++/Python语言,基于Qt5下原生QWidget编译开发,包含以下功能界面:包含Wi
    发表于 01-10 11:41 31次下载
    <b class='flag-5'>Qt</b> ECG Monitor <b class='flag-5'>Qt</b>嵌入<b class='flag-5'>式</b>床旁心电监护仪项目<b class='flag-5'>源码</b>

    记录整个Qt环境的搭建过程

    整个Qt环境安装过程大约花了一个小时,完成后,在Windows的『开始』菜单中也可以找到对应的快捷方式。至此,Qt 5.15就安装完成啦!
    的头像 发表于 09-05 15:13 1319次阅读

    QT设计的网络助手源码

    QT设计的网络助手源码
    发表于 09-27 11:46 2次下载

    qt设计的Google拼音输入法源码

    qt设计的Google拼音输入法源码分享
    发表于 09-26 17:40 1次下载

    qt opencv opencl opengl源码例程

    qt-opencv-opencl-opengl-源码例程
    发表于 09-27 14:42 1次下载