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. php中不借助IDE快速定位行数或者方法定义的文件和位置

    借助了ReflectionMethod的一些特性,可以快速获得函数或者方法是在哪个文件的什么位置定义的,对于调试没有文档的程序来说很有帮助! ---------------- function fun ...

  2. Eclipse导入MyEclipse项目遇到问题解答

    导入一个其他IDE中的项目,第一一定要想到:不同IDE中的配置文件问题: 比如:tomcat启动寻找项目的根目录问题,Eclipse的webContext,Myeclipse的是Webroot文件夹, ...

  3. 136. Single Number唯一的一个只出现了一次的数字

    [抄题]: Given a non-empty array of integers, every element appears twice except for one. Find that sin ...

  4. 默默的发现在网上找到的hook NtQueryDirectoryFile......

    默默的发现在网上找到的hook  NtQueryDirectoryFile...... hook  NtQueryDirectoryFile是为了实现文件隐藏,然后就发现在网上发现的代码版本似乎同一个 ...

  5. 5.WHERE 子句

    WHERE 子句用于规定选择的标准. WHERE 子句 如需有条件地从表中选取数据,可将 WHERE 子句添加到 SELECT 语句. 语法 SELECT 列名称 FROM 表名称 WHERE 列 运 ...

  6. hibernate mapping文件中 xmlns会导致linq to xml 查询不到对应的节点

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  7. Entity Framework 6.0 Tutorials(2):Async query and Save

    Async query and Save: You can take advantage of asynchronous execution of .Net 4.5 with Entity Frame ...

  8. C#获取文件的Content-Type(MIME Type)的方法

    使用静态类MimeMapping(需要.NET Framework 4.5及以后的支持) string fileName = "D:\myfile.txt"; var conten ...

  9. delphi 数组的使用

    delphi中数组就跟string使用类似,数组分为:动态数组和静态数组 还可根据数据的功能分为:数组(一维数组).二维数组.三维数组...静态数组: 固定长度,内容需要定义时添加.动态数组: 故名思 ...

  10. js工具库简单介绍

    javascript mvc的解决方案: angularjs, backbone,underscore, 有空的时候了解一下. 移动端的几个需要了解一下,jq mobile, zepto.knocko ...