Qt 学习之路 2(40):隐式数据共享

Qt 中许多 C++ 类使用了隐式数据共享技术,来最大化资源利用率和最小化拷贝时的资源消耗。当作为参数传递时,具有隐式数据共享的类即安全又高效。在数据传递时,实际上只是传递了数据的指针(这一切都是隐含帮你完成的),而只有在函数发生需要写入的情况时,数据才会被拷贝(也就是通常所说的写时复制)。本章我们将介绍有关隐式数据共享的相关内容,以便为恰当地使用前面所介绍的容器夯实基础。

具有数据共享能力的类包含了一个指向共享数据块的指针。这个数据块包含了数据本身以及数据的引用计数。当共享对象创建出来时,引用计数被设置为 1。当新的对象引用到共享数据时,引用计数增加;当对象引用不再引用数据时,引用计数减少。当引用计数变为 0 时,共享数据被删除。

在我们操作共享数据时,实际有两种拷贝对象的方法:我们通常称其为深拷贝和浅拷贝。深拷贝意味着要重新构造一个全新的对象;浅拷贝则仅仅复制引用,也就是上面所说的那个指向共享数据块的指针。深拷贝对内存和 CPU 资源都是很昂贵的;浅拷贝则非常快速,因为它仅仅是设置一个新的指针,然后将引用计数加 1。具有隐式数据共享的对象,其赋值运算符使用的是浅拷贝来实现的。

这种隐式数据共享的好处是,程序不需要拥有不必要的重复数据,减少数据拷贝的需求。重复数据的代价是降低内存使用率(因为内存存储了更多重复的数据)。通过数据共享,对象可以更简单地作为值来传递以及从函数中返回。

隐式数据共享是在底层自动完成的,程序人员无需关心。这也是“隐式”一词的含义。从 Qt4 开始,即使在多线程程序中,隐式数据共享也是起作用的。在很多人看来,隐式数据共享和多线程是不兼容的,这是由引用计数的实现方式决定的。但是,Qt 使用了原子性的引用计数来避免多线程环境下可能出现的执行顺序打断的行为。需要注意的是,原子引用计数并不能保证线程安全,还是需要恰当的锁机制。这种观点对所有类似的场合都是适用的。原子引用计数能够保证的是,线程肯定操作自己的数据,线程自己的数据是安全的。总的来说,从 Qt4 开始,你可以放心使用隐式数据共享的类,即使在多线程环境下。

我们可以使用QSharedDataQSharedDataPointer类实现自己的隐式数据共享类。

当对象即将被修改,并且其引用计数大于 1 时,隐式数据共享自动将数据从共享块中拿出。隐式共享类必须控制其内部数据,在任何修改其数据的函数中,将数据自动取出。(okgogo注:修改时被修改的类有一份深拷贝,从而不会影响共享数据块)

QPen使用了隐式数据共享技术,我们以QPen为例,看看隐式数据共享是如何起作用的:

 
 
void QPen::setStyle(Qt::PenStyle style)
{
detach(); // 从共享区取出数据
d->style = style; // 设置数据(更新)
}

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

1
2
3
4
5
6
7
8
9
10
11
12
void QPen::setStyle(Qt::PenStyle style)
{
    detach(); // 从共享区取出数据
    d->style = style; // 设置数据(更新)
}
 
void QPen::detach()
{
    if (d->ref != 1) {
        ... // 执行深拷贝
    }
}

凡是支持隐式数据共享的 Qt 类都支持类似的操作。用户甚至不需要知道对象其实已经共享。因此,你应该把这样的类当作普通类一样,而不应该依赖于其共享的特色作一些“小动作”。事实上,这些类的行为同普通类一样,只不过添加了可能的共享数据的优点。因此,你大可以使用按值传参,而无须担心数据拷贝带来的性能问题。例如:

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

QPainter paint;
paint.begin(&p2); // 从此,p2 与 p1 分道扬镳
paint.drawText(0,50, "Hi");
paint.end();

1
2
3
4
5
6
7
8
QPixmap p1, p2;
p1.load("image.bmp");
p2 = p1; // p1 和 p2 共享数据
 
QPainter paint;
paint.begin(&p2); // 从此,p2 与 p1 分道扬镳
paint.drawText(0,50, "Hi");
paint.end();

上例中,p1 和 p2 在QPainter::begin()一行之前都是共享数据的,直到这一语句。因为该语句开始,p2 就要被修改了。

注意,前面已经提到过,不要在使用了隐式数据共享的容器上,在有非 const STL 风格的遍历器正在遍历时复制容器。另外还有一点,对于QList或者QVector,我们应该使用at()函数而不是 [] 操作符进行只读访问。原因是 [] 操作符既可以是左值又可以是右值,这让 Qt 容器很难判断到底是左值还是右值,这意味着无法进行隐式数据共享;而at()函数不能作左值,因此可以进行隐式数据共享。另外一点是,对于begin()end()以及其他一些非 const 遍历器,由于数据可能改变,因此 Qt 会进行深复制。为了避免这一点,要尽可能使用const_iteratorconstBegin()constEnd()

Qt 学习之路 2(40):隐式数据共享的更多相关文章

  1. Qt 学习之路 2(15):标准对话框 QMessageBox

    Qt 学习之路 2(15):标准对话框 QMessageBox  豆子  2012年9月18日  Qt 学习之路 2  40条评论 所谓标准对话框,是 Qt 内置的一系列对话框,用于简化开发.事实上, ...

  2. Qt : 隐式数据共享(copy on write)

    copy on write 意思当内容有变动的时候,才对容器中的数据结构进行复制.否则仅作共享. QT许多类中使用了隐式数据共享技术,来最大化资源利用率和最小化拷贝时的资源消耗. 在数据传递时,其实只 ...

  3. Qt 学习之路 2(64):使用 QJsonDocument 处理 JSON

    Home / Qt 学习之路 2 / Qt 学习之路 2(64):使用 QJsonDocument 处理 JSON Qt 学习之路 2(64):使用 QJsonDocument 处理 JSON  豆子 ...

  4. Qt 学习之路 2(39):遍历容器

    Qt 学习之路 2(39):遍历容器 豆子 2013年1月16日 Qt 学习之路 2 29条评论 上一节我们大致了解了有关存储容器的相关内容.对于所有的容器,最常用的操作就是遍历.本章我们将详细了解有 ...

  5. Qt 学习之路 2(38):存储容器

    Qt 学习之路 2(38):存储容器 豆子 2013年1月14日 Qt 学习之路 2 38条评论 存储容器(containers)有时候也被称为集合(collections),是能够在内存中存储其它特 ...

  6. Qt 学习之路 2(32):贪吃蛇游戏(2)

    Qt 学习之路 2(32):贪吃蛇游戏(2) 豆子 2012年12月27日 Qt 学习之路 2 55条评论 下面我们继续上一章的内容.在上一章中,我们已经完成了地图的设计,当然是相当简单的.在我们的游 ...

  7. Qt 学习之路 2(29):绘制设备

    Qt 学习之路 2(29):绘制设备 豆子 2012年12月3日 Qt 学习之路 2 28条评论 绘图设备是继承QPainterDevice的类.QPaintDevice就是能够进行绘制的类,也就是说 ...

  8. Qt 学习之路 2(19):事件的接受与忽略

    Home / Qt 学习之路 2 / Qt 学习之路 2(19):事件的接受与忽略 Qt 学习之路 2(19):事件的接受与忽略  豆子  2012年9月29日  Qt 学习之路 2  140条评论 ...

  9. Qt 学习之路 2(16):深入 Qt5 信号槽新语法

    Qt 学习之路 2(16):深入 Qt5 信号槽新语法  豆子  2012年9月19日  Qt 学习之路 2  53条评论 在前面的章节(信号槽和自定义信号槽)中,我们详细介绍了有关 Qt 5 的信号 ...

随机推荐

  1. 如何使用ThinkPHP5 ,自动生成目录?

    具体步骤: A.在build.php中按照实际需求修改定义模块的内容: B.修改Public/index.php,在代码中加入: // 读取自动生成定义文件 $build = include '/.. ...

  2. linux的基本指令--第三节

    查找与检索: 一.文件名查找:find . -name "test*"      find 路径  查找类型  名字  未输入路径则默认当前路径 二 . 内容检索:grep  &q ...

  3. 调用DLL的2种方式

    [调用DLL的2种方式] DLL在生成的时候会有dll.lib2个文件,另外包含相应的.h. 1.静态方式,通过lib来引用dll,以及引入.h. 2.只通过dll来使用,前提是知道内部的函数符号.

  4. 【原创】1. MYSQL++简介

    MYSQL++是对于MYSQL C API的C++完全包装. MYSQL++能够至少做如下几件事情 1. 连接数据库 通过TCP连接数据库 通过WINDOWS命名管道连接数据库 UNIX域SOCKET ...

  5. mfs教程(三)

    mfs文件系统(三) 使用  MooseFS 一.挂载文件系统 启动管理服务器(master server)和数据服务器(chunkservers) (chunkservers一个是必需的,但至少两个 ...

  6. AntD02 Table组件的使用

    1 前提准备 1.1 创建一个angular项目 1.2 将 Ant Design 整合到 Angular 项目中 1.3 官方文档 点击前往 2 简单使用 <nz-table #rowSele ...

  7. mysql GROUP_CONCAT 可以将分组的字段进行拼接处理.

    GROUP_CONCAT 可以将分组的字段进行拼接处理. SELECT g.id, g.merchant_id, g. NAME, g.introduction, g.cover_pic, g.pla ...

  8. Suse系统磁盘文件损坏恢复

    进入救援(failSafe)模式检测问题,发现是因为/dev/sda4分区出现文件系统损坏.   /dev/sda4: UNEXPECTED INCONSISTENCY: run fsck manua ...

  9. =面试题:java面试基本方向 背1 有用 项目二技术学完再看

    一.Java基础 1. 集合框架A)集合中泛型优点? 将运行期的ClaasCastException 转到编译期异常.  泛型还提供通配符 1)HashMap---允许一个键为null,允许多个值为n ...

  10. 面试题:MySQL性能调优——索引详解与索引的优化 没用

    ——索引优化,可以说是数据库相关优化.理解尤其是查询优化中最常用的优化手段之一.所以,只有深入索引的实现原理.存储方式.不同索引间区别,才能设计或使用最优的索引,最大幅度的提升查询效率! 一.BTre ...