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的三种等待,强制等待、隐式等待、显式等待
为什么要设置元素等待 直白点说,怕报错,哈哈哈! 肯定有人会说,这也有点太直白了吧. 用一句通俗易懂的话就是:等待元素已被加载完全之后,再去定位该元素,就不会出现定位失败的报错了. 如何避免元素未加载 ...
随机推荐
- hdu 4771 Stealing Harry Potter's Precious (2013亚洲区杭州现场赛)(搜索 bfs + dfs) 带权值的路径
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4771 题目意思:'@' 表示的是起点,'#' 表示的是障碍物不能通过,'.' 表示的是路能通过的: ...
- todos Vue
<div id="todo-list-example"> <input v-model="newTodoText" v-on:keyup.en ...
- Ream的入门使用
一.介绍 Realm是一个不错的手机平台上的数据库,支持多种编程环境,如:Java.Object-C.React Native.Swift.Xamari等. Realm的官网:https://real ...
- android怎样写一个自己定义的dialog能够在Title的位置弹出来
先上效果图: Title的Layout为: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/andr ...
- Easyui Datagrid的Rownumber行号显示问题
Datagrid中当你的行数据超过9999时,第一列的行号rownumber将会因为表格内容过长而导致无法显示全部数字, 这一点Easyui无法做到自适应 所以需要进行修改,这里扩展一个方法就行了. ...
- php 路由实现
因为有小的业务逻辑比较复杂,orm用起来很麻烦,但是多入口又不好控制,每个页面都去包含,多了就迷了,所以就只写了个路由,加了个防注入 封装增删改查好无聊,直接封装业务逻辑,为业务而开发,业务逻辑里是面 ...
- IntelliJ IDEA JDK配置
1.JDK环境 目前大多数IDE都没有集成JDK环境,IDEA也是一样,在使用IDEA之前首先要安装JDK,并且配置环境变量.与其他IDE不同之处在于,IDEA不会自动匹配系统的JDK环境,编译时会提 ...
- SQL2005 第一次配置没有服务器名称的问题
问题描述:第一次启动没有服务器名称 解决方法: 1.进入 我的电脑——属性——管理——服务 找到SQL Server 右键属性 弹出下图 找到可执行文件路径 鼠标左键拖到底部 看到 -s实例名,这里的 ...
- 8148 add spi driver
http://blog.csdn.net/zouwen198317/article/details/8452209 http://e2e.ti.com/support/dsp/davinci_digi ...
- 【BZOJ】2018: [Usaco2009 Nov]农场技艺大赛(暴力)
http://www.lydsy.com/JudgeOnline/problem.php?id=2018 精度问题我也是醉了.. #include <cstdio> #include &l ...