Qt隐式共享与显式共享
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Amnes1a/article/details/69945878
Qt中的很多C++类都使用了隐式数据共享来最大化资源使用和最小化拷贝代价。隐式共享类在作为参数传递时,不仅安全而且高效,因为只是指向数据的指针被传递了,底层的数据只有在函数向它执行写动作时才会发生拷贝,即写时拷贝。
一个共享类是由一个指向共享数据块的的指针组成的,该数据块包含一个引用计数和实际数据。
当一个隐式共享类的对象被创建时,它会引用计数设为1。无论何时,当一个新的对象引用这个共享数据时,引用计数会增加,当一个对象解引用这个共享数据时,引用计数会减少。当引用计数变为0时,共享数据会被删除。
当处理共享数据时,通常有两种方式拷贝一个对象。我们通常称它们为深拷贝和浅拷贝。其中,深拷贝意味着复制一个对象;浅拷贝只拷贝引用,即只有指向共享数据的指针被复制。但执行一次深拷贝在内存和CPU方面的代价都是昂贵的。执行一次浅拷贝是非常快的,因为这只牵涉到设置一个指针和增加引用计数。隐式共享对象的赋值(使用operator=)被实现为浅拷贝。
共享的好处就在于程序不必执行不必要的拷贝,这就会促使更少的内存使用和更少的数据复制。这样,对象就可以简单的被赋值,作为函数的参数传递,作为函数的返回值。
隐式共享大多发生的背后,编程人员一般不需要关注它们。但是,隐式共享导致Qt的容器类和STL中的容器类有很大的不同。由于隐式共享,当复制一个容器时,它们其实是共享一份数据的。如下代码所示:
QVector<int> a, b;
a.resize(100000); // make a big vector filled with 0.
QVector<int>::iterator i = a.begin();
b = a;
此处,迭代器i的使用要格外小心,因为它指向了共享数据。如果我们指向*i = 4,我们改变的将会是共享的实体,即会影响到两个容器。
其实,在多线程应用程序中,隐式共享也会发生。从Qt4开始,隐式共享类就可以在线程间安全的拷贝,它们完全是可重入的。在很多人看来,考虑到引用计数的行为,隐式共享和多线程应该是不兼容的概念。但其实,Qt使用了原子引用计数来确保共享数据的完整性,避免了引用计数的潜在的错误。但原子引用计数并不能保证线程安全。当在线程间共享一个隐式共享类的实例时,还是应该使用合适的锁。在对于所有的可重入类来说都是必须的,无论共享或不共享。但是,原子引用计数可以保证线程作用于它自己本地的一个隐式共享类的实例是安全的。我们通常建议使用信号和槽在线程间传递数据,因为这不需要任何显式的锁机制。
当然,除了Qt自带的类的隐式共享,我们还可以使用QSharedData和QSharedDataPointer这两个类来实现我们自己的隐式共享。
如果对象将要被改变并且其引用计数大于1,隐式共享会自动的从共享块中分离该对象。(这经常被称为写时复制)
隐式共享类可以控制它自己的内部数据。在它的要修改数据的成员函数中,它会在修改数据之前自动的分离。但是,请注意,对于容器类的迭代器来说比较特殊,参见上面所讲。
下面,我们以一个员工类为例,来实现一个隐式共享类。步骤如下:
定义类Emplyee,该类只有一个唯一的数据成员,类型为QSharedDataPointer<EmployeeData>。
定义类EmployeeData类,其派生自QSharedData。该类中包含的就是原本应该放在Employee类中的那些数据成员。
类定义如下:
#include <QSharedData>
#include <QString>
class EmployeeData : public QSharedData
{
public:
EmployeeData() : id(-1) { }
EmployeeData(const EmployeeData &other)
: QSharedData(other), id(other.id), name(other.name) { }
~EmployeeData() { }
int id;
QString name;
};
class Employee
{
public:
Employee() { d = new EmployeeData; }
Employee(int id, const QString &name) {
d = new EmployeeData;
setId(id);
setName(name);
}
Employee(const Employee &other)
: d (other.d)
{
}
void setId(int id) { d->id = id; }
void setName(const QString &name) { d->name = name; }
int id() const { return d->id; }
QString name() const { return d->name; }
private:
QSharedDataPointer<EmployeeData> d;
};
在Employee类中,要注意这个数据成员d。所有对employee数据的访问都必须经过d指针的operator->()来操作。对于写访问,operator->()会自动的调用detach(),来创建一个共享数据对象的拷贝,如果该共享数据对象的引用计数大于1的话。也可以确保向一个Employee对象执行写入操作不会影响到其他的共享同一个EmployeeData对象的Employee对象。
类EmployeeData继承自QSharedData,它提供了幕后的引用计数。
在幕后,无论何时一个Employee对象被拷贝、赋值或作为参数传,QSharedDataPointer会自动增加引用计数;无论何时一个Employee对象被删除或超出作用域,QSharedDataPointer会自动递减引用计数。当引用计数为0时,共享的EmployeeData对象会被自动删除。
void setId(int id) { d->id = id; }
void setName(const QString &name) { d->name = name; }
在Employee类的非const成员函数中,无论何时d指针被解引用,QSharedDataPointer都会自动的调用detach()函数来确保该函数作用于一个数据拷贝上。并且,在一个成员函数中,如果对d指针进行了多次解引用,而导致调用了多次detach(),也只会在第一次调用时创建一份拷贝。
int id() const { return d->id; }
QString name() const { return d->name; }
但在Employee的const成员函数中,对d指针的解引用不会导致detach()的调用。
还有,没必要为Employee类实现拷贝构造函数或赋值运算符,因为C++编译器提供的拷贝构造函数和赋值运算符的逐成员拷贝就足够了。因为,我们唯一需要拷贝的就是d指针,而该指针是一个QSharedDataPointer,它的operator=()仅仅是递增了共享对象EmployeeData的引用计数。
隐式共享 VS 显式共享
上面讲到的隐式共享,对于Employee类来说可能会有问题。考虑一种情况,如下代码所示:
#include "employee.h"
int main()
{
Employee e1(1001, "Tom");
Employee e2 = e1;
e1.setName("Jerry");
}
在创建e2,并将e1赋值给它后,e1和d2都引用了同一个员工,即Tom,1001。这两个Employee对象指向了同一个EmployeeData实例,所以该实例的引用计数为2。紧接着执行e2.setName("Jerry")来改变员工名字,但因为此时的引用计数大于1,所以会在名字发生改变前执行一次写时复制,使e1和e2指向不同的EmployeeData对象,然后对e1执行名字修改动作。从而导致e1和e2有不同的名字,但有相同的ID,1001,这可能不是我们想要的。当然,如果我们确实想创建出第二个完全不同的员工,那么可以再在e1上调用setId(1002),修改其ID。但是,如果我们就是只想改变员工的名字呢?此时,我们就可以考虑使用显式共享来替代隐式共享。
如果我们在Employee类中的声明d指针时使用的是QExplicitySharedDataPointer<EmployeeData>,那么就是使用了显式共享,写时复制操作就不会自动发生了(即 在 非 const成员函数中不会自动调用detach())。这样一来,e1.setName("Jerry")执行之后,员工的名字被改变了,但e1和e2仍然引用同一个EmployeeData实例,故还是只有一个id为1001的员工。
---------------------
作者:求道玉
来源:CSDN
原文:https://blog.csdn.net/Amnes1a/article/details/69945878
版权声明:本文为博主原创文章,转载请附上博文链接!
Qt隐式共享与显式共享的更多相关文章
- [转]静态库、动态库,dll文件、lib文件,隐式链接、显式链接
转自:https://blog.csdn.net/dcrmg/article/details/53427181 静态链接.动态链接 静态库和动态库分别应用在静态链接方式和动态链接方式中,所谓静态链接方 ...
- 静态库、动态库,dll文件、lib文件,隐式链接、显式链接浅见
静态链接.动态链接 静态库和动态库分别应用在静态链接方式和动态链接方式中,所谓静态链接方式是指在程序执行之前完成所有的链接工作,把静态库一起打包合入,生成一个可执行的目标文件(EXE文件).所谓动态链 ...
- (4.19)sql server中的事务模式(隐式事务,显式事务,自动提交事务)
(4.19)sql server中的事务模式(隐式事务,显式事务,自动提交事务) 1.概念:隐式事务,显式事务,自动提交事务 2.操作:如何设置事务模式 3.存储过程中的事务 XACT_ABORT 1 ...
- js 五种绑定彻底弄懂this,默认绑定、隐式绑定、显式绑定、new绑定、箭头函数绑定详解
壹 ❀ 引 可以说this与闭包.原型链一样,属于JavaScript开发中老生常谈的问题了,百度一搜,this相关的文章铺天盖地.可开发好几年,被几道this题安排明明白白的人应该不在少数(我就是 ...
- selenium—隐式等待和显式等待
一.隐式等待和显式等待的区别 隐式等待:是整个页面的等待.设置一个最长的等待时间,在规定时间内整个页面加载完成,则执行下一步,否则继续等待直到最长等待时间结束. 显式等待:是针对某个元素的等待.在设置 ...
- Selenium系列(六) - 强制等待、隐式等待、显式等待
如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...
- selenium(5)-解读强制等待,隐式等待,显式等待的区别
背景 为什么要设置元素等待 因为,目前大多数Web应用程序都是使用Ajax和Javascript开发的:每次加载一个网页,就会加载各种HTML标签.JS文件 但是,加载肯定有加载顺序,大型网站很难说一 ...
- JS五种绑定彻底弄懂this,默认绑定、隐式绑定、显式绑定、new绑定、箭头函数绑定详解(转载)
目录 壹 ❀ 引 贰 ❀ this默认绑定 叁 ❀ this隐式绑定 1.隐式绑定 2.隐式丢失 肆 ❀ this显式绑定 伍 ❀ new绑定 陆 ❀ this绑定优先级 柒 ❀ 箭头函数的this ...
- Selenium4+Python3系列(六) - Selenium的三种等待,强制等待、隐式等待、显式等待
为什么要设置元素等待 直白点说,怕报错,哈哈哈! 肯定有人会说,这也有点太直白了吧. 用一句通俗易懂的话就是:等待元素已被加载完全之后,再去定位该元素,就不会出现定位失败的报错了. 如何避免元素未加载 ...
随机推荐
- 多线程-Executor,Executors,ExecutorService,ScheduledExecutorService,AbstractExecutorService
引用 系统启动一个新线程的成本是比较高的,因为涉及与操作系统交互.使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短的线程时,更应该考虑使用线程池.线程池在系统启动时即创建大量空闲的线 ...
- atitit.jQuery Validate验证框架详解与ati Validate 设计新特性
atitit.jQuery Validate验证框架详解与ati Validate 设计新特性 1. AtiValidate的目标1 2. 默的认校验规则1 2.1. 使用方式 1.metadata用 ...
- ArrayList 和 HashMap 的默认大小是多数?
ArrayList 和 HashMap 的默认大小是多数? 在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂).这就是 Jav ...
- 解决 Netbeans Ant: taskdef class org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs cannot be found
你在用Netbeans(实际上是Ant)Clean and Build你的项目生成可执行文件(例如Windows下的exe文件)时候遇到报错 或者遇到这样的报错: The libs.CopyLibs. ...
- RandomUser 网站介绍
RandomUser 网站介绍 tools api 介绍 使用 结果 API 错误 请求多个用户 指定性别 密码 种子 格式 使用早期版本 国家 页码 包含/不包含字段 杂项 结束语 介绍 在 201 ...
- 解决Linux环境下Tomcat启动卡住问题
最近发现在服务器上启动tomcat,会存在卡住的情况,这种情况是每次必现,通过搜索发现是随机数生成问题.解决方案如下 将$JAVA_HOME/jre/lib/security/Java.securit ...
- 【Unity】Unity中资源动态载入的两种方式之AssetsBundle
首先要说的是,我们的project中有2个脚本.各自是: Build(编辑器类脚本.无需挂载到不论什么物体).可是必需要把Build脚本放到Editor目录中 Load脚本,挂载到摄像机上<pr ...
- linux设置时间的方法
0. date -R 中国上海的时区是+8000 1.tzselect 设置时区,依次选择5,9,1,1(如果时区不一样,执行下面的命令得到之后时间是不一样的) 2.sudo ntpdate asi ...
- Github使用之git回退到某个历史版本
1. 查找历史版本 使用git log命令查看所有的历史版本,获取你git的某个历史版本的id 假设查到历史版本的id是fae6966548e3ae76cfa7f38a461c438cf75ba965 ...
- 成功抓取douban 所有电影
之前爬了250,想爬所有的电影 Rule(LinkExtractor(allow=(r'https://movie.douban.com/subject/\d+')), callback=" ...