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. zookeeper伪集群的搭建

    由于公司服务器数量的限制,我们往往没有那么多的服务器用来搭建zookeeper的集群,所以产生了伪集群的搭建,也就是将多个zookeeper搭建在同一台机器上. 准备工作: 1,一台服务器,我们这里用 ...

  2. mybatis的执行流程 #{}和${} Mysql自增主键返回 resultMap 一对多 多对一配置

    n Mybatis配置 全局配置文件SqlMapConfig.xml,配置了Mybatis的运行环境等信息. Mapper.xml文件即Sql映射文件,文件中配置了操作数据库的Sql语句.此文件需要在 ...

  3. 面试题:SSH项目总结 !=!=未看 没用

    阿玻罗软件(上海)有限公司已经两年了.中国银行营销系统,到民生银行小微信贷工厂建设.再到交通银行ioffice移动平台项目.以前所学的SSH好多都用不上 公司的框架.都是负责项目的贷款查找模块开发和测 ...

  4. 安装 SQL Server 2014 Express

    安装 SQL Server 2014 Express 我的电脑系统: Windows 10 64位 一 . 下载 安装Microsoft SQL Server 2014 Express 软甲下载地址: ...

  5. How to install Freemind 1.0.1 to Ubuntu 14

    安装了Freemind0.9后发现不能打开windows的1.0.1保存的*.mm文件,便对版本开始升级. 1. 从http://freemind.sourceforge.net/wiki/index ...

  6. PyV8在服务端运行自动崩溃问题

    近来想在服务端架设WSGI + PyV8去自动解析JavaScript代码,然后返回解析后的数据给客户端.但是发现,在nginx配置后,客户端一请求,服务端的python脚本自动崩溃. 见代码: de ...

  7. 类操作,removeClass&addClass

    // 添加类 function addClass(node,className){                 var reg=new RegExp("\\b"+classNa ...

  8. MVC复杂类型的模型绑定

    1,属性为引用类型(非集合,非数组) //模型1 public class Contact { public string Name { get; set; } public string Phone ...

  9. 从程序员角度看ELF

    原文:http://xcd.blog.techweb.com.cn/archives/222.html 特殊说明(by jfo) 对于static-linked或shared-linked的ELF可执 ...

  10. .net Stream篇(七)

    NetworkStream 目录: NetworkStream的作用 简单介绍下TCP/IP 协议和相关层次 简单说明下 TCP和UDP的区别 简单介绍下套接字(Socket)的概念 简单介绍下Tcp ...