QTL 容器 与 STL(1)- 写时复制

Qt基础之四十一:记一次QVector的bad alloc问题追踪 记一次QVector的bad alloc问题追踪 阅读详情

QTL 与 STL 最大的区别之一

隐式共享,引用计数,写时复制

【注】STL 的 string 也具有 写时复制 技术

 

写时复制

  (Copy-On-Write)技术,就是编程界“懒惰行为”——拖延战术的产物。

   1、复制的时候共享内存,只有修改(或 [ ] 操作)的时候,才分配新内存

QVector<int> v1;
v1 << 1 << 2 << 3 << 4 << 5;
QVector<int> v2(v1);          // 此时v2与v1共享数据(内存)
v2[1] = 8;                    // 写动作产生,v2被分配新内存
qDebug() << v1 << endl << v2;

 A) 如下图所示,将 v1 复制给 v2时 这两个容器的地址是一样的:

  B)使用 [ ] 操作时,给 v2 重新分配内存,两个容器的地址不一样:

  2、写时复制的情况下,同时改变多个容器的值:

// 第一种情况;
QVector<int> v1;
v1 << 1 << 2 << 3 << 4 << 5;
int *p = &v1[1];                 // 声明一个指针指向 v1 的第二个数据
QVector<int> v2(v1);      // 此时v2与v1共享数据(内存)
*p = 8;                              // 使用指针对 v1 数据进行修改
qDebug() << v1 << endl << v2;

// 第二种情况;
QVector<int> aV, bV;
aV.resize(20);

QVector<int>::iterator i = aV.begin();
bV = aV; //此时 aV bV 共享数据(内存);
*i = 4; //此时容器 aV bV 的数据同时改变了;

我们用指向v1的指针修改v1,结果v1与v2的数据都被改变了。原因就在于“利用指针修改内存值”这种写行为无法被QVector类侦测到,因而不能触发其复制机制,当我们在使用qDebug输出v1与v2的值时,他们两者依然共享着同一段内存,因此输出相同的结果。而这种结果大多数情况下都不是程序员想要的,编写Qt代码应该十分小心这个问题。

【注意】

对于 QList 或者 QVector,我们应该使用 at() 函数而不是  []  操作符进行只读访问。

原因是 [] 操作符既可以是左值又可以是右值,这让 Qt 容器很难判断到底是左值还是右值,这意味着无法进行隐式数据共享;

而 at() 函数不能作左值,因此可以进行隐式数据共享。

另外一点是,对于 begin(),end() 以及其他一些非 const 遍历器,由于数据可能改变,因此 Qt 会进行深复制。为了避免这一点,要尽可能使用 const_iterator、constBegin() 和 constEnd()

3、规避 无法发生的情况【需要重载多个函数,不方便使用,故 QTL未实现】

解决方案:从重载operator[]入手。

A)但是这个操作符比较特殊,比如在

int i = v2[1];

所示的情况下,作为只读情况,我们不需要让v2独立出来,因此无需复制。

B)而在下面这种情况,作为写入的情况,v2中的内容即将被修改,因此需要马上复制出自己一份独立的数据出来。

v2[1] = 10;

C)剩下还有一种就上面所示的情况了,v1[1]被指针定位,我们根本不能确定用户取到它之后会不会修改它,如果不修改而我们又在operator[]内做了复制工作岂不是浪费;如果修改了,我们却在当时没做复制工作,之后就没机会了,就像我们上面看到的例子一样。

遗憾的是,C++并不能区分[]符是在以上哪种情况中被调用的,一概复制可能会浪费,一概不复制又会出问题,怎么做呢,QByteArray的设计给了我们答案(QString类似,至于QVector等容器并未采用此方法的原因,后述)。

我们知道对QByteArray调用[]会返回一个char,因此可以写段类似的代码看看:

QByteArray str1("HelloWorld");
QByteRef c = str1[2];
QByteArray str2(str1);
c = 'M';
qDebug() << str1 << " " << str2;

查看打印结果,没有问题,只有str1被修改了。

在考虑QByteRef到底是个什么东西的时候,我们回头来思考之前的问题:虽然我们不能确定operator []是在左值还是右值的情况被调用,但是我们可以让这个函数返回一个代理类(Proxy Class),然后等待看看这个Proxy类如何被运用——如果它被读取,我们就将operator[]的调用视为一个读取动作,如果它被写,我们就将operator[]的调用视为一个写动作,执行复制行为。好吧,正如你所猜想的,QByteRef正是这样的一个代理类。

QByteArray中重载operator[]的代码如下,除了返回一个QByteRef对象之外什么也没做:

inline QByteRef QByteArray::operator[](int i)
{ Q_ASSERT(i >= 0); return QByteRef(*this, i); }
inline QByteRef QByteArray::operator[](uint i)
{ return QByteRef(*this, i); }

而QByteRef作为QByteArray的内嵌类不到20行:

class Q_CORE_EXPORT QByteRef {
    QByteArray &a;
    int i;
    inline QByteRef(QByteArray &array, int idx)
        : a(array),i(idx) {}
    friend class QByteArray;
public:
    inline operator char() const
        { return i < a.d->size ? a.d->data()[i] : char(0); }
    inline QByteRef &operator=(char c)
        { if (i >= a.d->size) a.expand(i); else a.detach();
          a.d->data()[i] = c;  return *this; }
    inline QByteRef &operator=(const QByteRef &c)
        { if (i >= a.d->size) a.expand(i); else a.detach();
          a.d->data()[i] = c.a.d->data()[c.i];  return *this; }
    inline bool operator==(char c) const
    { return a.d->data()[i] == c; }
    inline bool operator!=(char c) const
    { return a.d->data()[i] != c; }
    inline bool operator>(char c) const
    { return a.d->data()[i] > c; }
    inline bool operator>=(char c) const
    { return a.d->data()[i] >= c; }
    inline bool operator<(char c) const
    { return a.d->data()[i] < c; }
    inline bool operator<=(char c) const
    { return a.d->data()[i] <= c; }
};

当QByteRef作为右值时(被读取),operator char()被调用,QByteRef被隐式转换为char,直接取出其内部数据;当operator =被调用时,意味着QByteRef现在作为左值(被写入),因此调用detach()函数将共享内存分离并进行写入。


现在我们可以轻松地分辨operator []的左值与右值运用了,但当然这也是有的弊端的,原先我们使用v1[1]的方式取出来的就是原始数据类型,比如QVector<int>,我们可以对v1[1]使用+,-,++,--,等等操作符,如果是QVector<MyClass>还可以调用我们自己定义的member function。但是我们一旦开始使用代理类,如果你不同意,编译器可不会让++,--,<,>等东西施加在QXXRef这种类型上。如果我们还想按原来的方式使用,就得重载一大堆函数了,就如上面的代码中后面的代码所示。

好在呢,QByteArray、QString与其他QTL不同,它们内部总是char类型数据,因此重载char相关的操作符就可以了。而QVector、QList等等这种内部数据类型由用户决定的容器就不方便这么做了,也是解释上面QVector为什么不使用代理类的原因。因此,在使用QVector、QList等模板类时,使用指针修改可能已经被隐式共享的对象时,一定要多加小心。

上海交大计算机保研面试 2026:3类专项计划与10个联培方向深度解析 本文深度解析2026年上海交通大学计算机保研面试的3类专项计划与10个联培方向,包括区块链技术、智能计算系统和网络安全专项,以及蚂蚁科技、苏州智研院等联培基地。文章详细介绍了各专项的核心课程、硬件资源和就业去向,为计算机专业学生提供全面的申请策略和职业发展建议。 阅读详情

相关推荐

开源处理器Rocket的分支预测机制研究与性能评估(二)

Rocket是基于RISC-V指令集架构的开源处理器,具有分支预测功能,其实现了GShare分支预测机制,在分析Rocket处理器分支预测处理过程、分支预测实现原理的基础上,利用模拟器进行了性能测试,并依据测试结果,对Rocket处理器分支预测参数配置给出建议。

leishangwen的专栏 5691

QT字节数组篇:相关基础函数学习

【代码】QT字节数组篇:相关基础函数学习。

qq_43279295的博客 1488

10xGenomics官方可视化工具——Loupe Browser

Loupe Browser是⼀款10xGenomics开发的交互式桌⾯应⽤程序,⽤于探索10x单细胞数据,可以用来找出感兴趣的细胞、找出显著差异表达的基因、鉴定细胞类型、探索细胞亚类和数据共享。

weixin_47195452的博客 3358

“QByteRef::operator ==”: 2 个重载有相似的转换报错解决方法

错误代码: void dealData(QByteArray data) { if(data[1] == 8) { return; } } 改正后的代码: void dealData(QByteArray data) { if(data.at(1) == 8) { return; } }

weixin_41275864的博客 1547

cesium 高程数据使用

cesium 高程数据使用高程数据下载高程数据切片简单数据服务 高程数据下载 使用地理空间数据云平台免费提供的高程数据。地理空间云平台 点击列表中的下载得到数据压缩包。例如:ASTGTM2_N22E114.zip 高程数据切片 使用cesium实验室工具对下载得到的压缩包进行切片。cesium实验室 切片得到的文件夹目录。 简单数据服务 使用nodejs的koa启动简单的资源服务。koajs 项目根目录新建data文件夹,在新建dem文件夹放高程数据。 注意目录自行更换,这里是./data/dem

哎哟这个凉白白的博客 1812

Nginx配置Cookie安全属性:HttpOnly、Secure、SameSite实战指南

Cookie是Web应用中维持用户状态的核心机制,其安全配置直接关系到会话管理与身份认证的可靠性。从原理上讲,HttpOnly属性可防止XSS攻击窃取Cookie,Secure属性确保Cookie仅在HTTPS加密连接中传输,而SameSite属性则用于控制跨站请求时Cookie的发送行为,能有效防御CSRF攻击。这些安全属性的技术价值在于构建纵深防御体系,提升应用的整体安全性。在现代浏览器(如Chrome 80+)默认将未明确声明的Cookie视为SameSite=Lax的背景下,正确配置这些属性变得尤为

weixin_33783283的博客 323

QString、QBitArray

一、初始化QString2、根据QChar[]初始化:3、索引方式初始化4、用其他字符集字符初始化QString默认是Unicode字符的。以下三个函数将字符串转成其他字符集的版本,它们以QByteArray的形式返回字符串的const char*版本:toUtf8()、toLatin1()和toLocal8Bit()。1、resize()重设大小。新分配的字符具有未定义的值。重设大小。新分配的字符具使用参数2初始化。将字符串中的每个字符设置为字符c。

hw5230的博客 618

华为OD机试真题 新系统 2026-09-06 Python&JS【仓储货物有序出库】

某仓储中心有一个货物堆放区,货物编号按乱序堆叠存放,只能从顶部取货,管理员需要将货物按编号从小到大取出。

MISAYAONE的博客 116

Qt Mac 桌面版本编译出错

新买的Mac电脑,安装xcode和Qt,环境配置完成后,新建一个Qt工程并编译Mac桌面版本,然后报错,信息如下: Qt Creator - Project ERROR: Xcode not set up properly. You may need to confirm the license agreement by running /usr/bin/xcodebuild

luoyayun361的专栏 1166

会计-合并-5- 增资、处置交易在合报与个报会计处理

摘要:本文系统梳理了股权投资会计处理的四种情形:从交易性金融资产转为权益法或成本法、权益法转成本法、权益法增持。核心原则是区分个报与合报处理,重点关注原投资公允/账面价值的计量差异及损益确认逻辑。对不丧失控制权的股权处置,个报确认投资收益,合报调整资本公积;丧失控制权时,个报需追溯调整权益法核算,合报计算综合投资收益。一揽子交易的特殊处理强调将过渡性差额暂记其他综合收益,最终丧失控制权时转入当期损益。文末通过典型案例演示了不同情境下的具体计算方法。

qq_34047402的博客 466

Windows密码忘了怎么办?一分钟看懂Windows密码底层机制:无需重装系统也能无痕找回登录密码

忘记Windows开机密码,不一定非得重装系统!本文手把手教你通过提取NTLM Hash值实现“无痕找回密码”,无需PE、无需清除密码,对系统零修改 🛡️ 搭配mimikatz与在线解密平台,小白也能快速上手,了解Windows密码底层机制 🔐

XiaoqiangClub的博客 126

Using QByteRef with an index pointing outside the valid range of a QByteArray

使用5.12.0时,没有报错,但是程序换到5.14.0就报错 Using QByteRef with an index pointing outside the valid range of a QByteArray. The corresponding behavior is deprecated, and will be changed in a future version of Qt. 原因:并没有给QByteArray初始化或分配空间,然后就使用[]运算符重载来替换掉原来的元素。所以造成了:指向了

qq_41622002的博客 1万+

【HYSPLIT 教程第二期】Inline WRF-HYSPLIT 耦合

【HYSPLIT 教程第二期】Inline WRF-HYSPLIT 耦合

WW、forever的博客 810

再谈QVector与QByteArray——Qt的写时复制(copy on write)技术

我们在之前的博文QVector的内存分配策略 与再谈QVector与std::vector——使用装饰者让std::vector支持连续赋值简单聊了聊QVector内存分配和赋值方面的一点东西,今天接着从QVector展开谈谈Qt的写时复制技术...

Cloud_Castle的博客 1万+

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

QVector与QByteArray——Qt的写时复制(copy on write)技术

Qt作为一个优秀的跨平台开源C++框架,如果我们只停留在使用它的基础上而不深挖其实现手法,实在是浪费这个知识宝库了~今天接着从QVector展开谈谈Qt的写时复制技术。老实说,“隐式共享,引用计数,写时复制”也是老调重弹的话题了,不过也是QTL与STL最大的区别之一,这篇博文不详谈“写时复制”技术的细节,那个有不少文章介绍过了,我们扯点关于operator[]和QByteRef和QStringRe...

南方以北 2704

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

[转]QVector与QByteArray——Qt的写时复制(copy on write)技术

我们在之前的博文QVector的内存分配策略与再谈QVector与std::vector——使用装饰者让std::vector支持连续赋值中简单聊了聊QVector内存分配和赋值方面的一点东西,今天接着从QVector展开谈谈Qt的写时复制技术。老实说,“隐式共享,引用计数,写时复制”也是老调重弹的话题了,不过也是QTL与STL最大的区别之一,这篇博文不详谈“写时复制”技术的细节,那个有不少文章...

weixin_33739523的博客 1154

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Hello Qt(二十四)——QT模板库

一、STL简介 C++标准委员会定义了STL的接口标准,但STL的具体实现依赖于编译器生产厂商,即不同编译器厂商的STL版本具有相同的全局函数、相同的算法类和数据结构类、相同的类成员函数。因此不同的编译器厂商的STL存在差异,依赖于编译器的STL开发的应用程序在不同平台上的行为可能存在差异。 二、QTL模板库简介 QT模板库(QT Template Library 简称QTL)是一套提供对象容器的模板。 QTL提供了对象的链表、对象的矢量(动态数组)、从一个类型到另一个类型的映射(或称为字典)和相关

天山老妖 1847

QT 容器 QList,QLinkedList,QVector,QMap,QHash优缺点,与使用范围

https://qtdoc.cn/Src/C/Container_Classes/Container_Classes.html 一、前言: 如果想看 STL容器 的优缺点,请查看如下文章: 《C++ STL容器 底层数据结构》 https://blog.youkuaiyun.com/LearnLHC/article/details/89552420?spm=1001.2014.3001.5502 《c++ map、multimap 的使用方法》 https://blog.youkuaiyun.com/LearnLHC..

LearnLHC的博客 1608

QT开发(二十五)——QT模板库

QT模板库(QT Template Library 简称QTL)是一套提供对象容器的模板。

pingis58的博客 1431

Qt信号槽的一些事

注:此文是站在Qt5的角度说的,对于Qt4部分是不适用的。 1.先说Qt信号槽的几种连接方式和执行方式。 1)Qt信号槽给出了五种连接方式: Qt::AutoConnection 0 自动连接:默认的方式。信号发出的线程和糟的对象在一个线程的时候相当于:DirectConnection, 如果是在不同线程,则相当于QueuedConnection

phenixyf的专栏 455

QVector的内存分配策略

我们都知道 std::vector 作为动态数组在所分配的内存被填满时,如果继续添加数据,std::vector 会另外申请一个大小当前容量两倍的区域(如果 n > size 则申请 n+当前容量 的空间),然后把当前内容拷贝到新的内存,以达到动态扩容的效果: size_type _M_check_len(size_type __n, const char* __s) const

Cloud_Castle的博客 1万+

Qt文档阅读笔记-隐式共享(Implicit Sharing)深入研究(理论及实例)

Qt里面很多C++类都是采用隐式共享最大限度的提高资源利用率以及最新复制的原则。隐式共享数据作为参数时,不仅安全而且高效,因为传的是一个指针并且只有要修改这个数据时才会去拷贝,这里有个专业词汇copy-on-write在Qt官方提供的ppt中经常会出现这个词,说的就是隐式共享。 共享类(这个应该是指valuetype的类,学过Qt的都知道,Qt类可以分为两类,一个是identitytype和valuetype)由一个共享数据块及指向这个块的计数器组成。 当共享对象被创建,这个块的计数器的值会...

IT1995的博客 1844

【qt】 error: C2666: “QByteRef::operator ==”: 2 个重载

问题: QByteArray msg; if(0xEB == msg[0]) { } 报错:“error: C2666: “QByteRef::operator ==”: 2 个重载” 解决: 改为 QByteArray msg; if(0xEB == msg.at(0)) { }

qq_43248127的博客 1226
上一篇: Qt 生成 Word、PPT、PDF 文档
下一篇: 【数学】弧度和角度的转换
LearnLHC
博客等级 码龄10年 183粉丝 149原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值