一、Qt隐式共享介绍
Qt中的许多C++类使用隐式数据共享来最大化资源使用并最小化复制。当作为参数传递时,隐式共享类既安全又高效,因为只传递一个指向数据的指针,并且只有当函数写入数据时,数据才会被复制,即write -on-write。
共享类由一个指向包含引用计数和数据的共享数据块的指针组成。
当创建共享对象时,它将引用计数设置为1。每当新对象引用共享数据时,引用计数就递增,当对象解引用共享数据时,引用计数就递减。当引用计数变为零时,将删除共享数据。
在处理共享对象时,有两种方法复制对象。我们通常谈论:深度拷贝和浅拷贝。深层复制意味着复制一个对象。浅拷贝是一个引用拷贝,也就是一个指向共享数据块的指针。在内存和CPU方面,制作一个深度拷贝可能是昂贵的。进行浅拷贝非常快,因为它只涉及设置指针和增加引用计数。
注意:隐式共享对象的对象赋值(使用operator=())是使用浅拷贝实现的。
隐式共享的好处是程序不需要进行不必要地数据复制操作,从而减少内存的使用和数据的复制。此外,对象可以很容易地被赋值,作为函数参数传递,并从函数中返回。
二、代码示例
1.QMap
隐式共享大多发生的背后,编程人员一般不需要关注它们。但是,隐式共享导致Qt的容器类和STL中的容器类有很大的不同。由于隐式共享,当复制一个容器时,它们其实是共享一份数据的。如下代码所示:
QMap<int, QString> aMap;
aMap[0] = "a";
aMap[1] = "b";
QMap<int, QString>::iterator iter = aMap.begin();
QMap<int, QString> bMap = aMap;
iter.value() = "c";
此处,迭代器iter的使用要格外小心,因为它指向了共享数据。如果我们iter.value() = "c";,我们改变的将会是共享的实体,即会影响到两个容器。所以这个时候aMap和bMap都被影响了。
我们将代码稍作修改:
QMap<int, QString> aMap;
aMap[0] = "a";
aMap[1] = "b";
QMap<int, QString> bMap = aMap;
QMap<int, QString>::iterator iter = aMap.begin();
iter.value() = "c";
qDebug() << bMap;
此时,迭代器iter指向的是aMap,并不会修改bMap的数据。
还有一种方式,调用detach接口分离。
QMap<int, QString> aMap;
aMap[0] = "a";
aMap[1] = "b";
QMap<int, QString>::iterator iter = aMap.begin();
QMap<int, QString> bMap = aMap;
bMap.detach();
iter.value() = "c";
以上结果可以发现,只有aMap被改变了,bMap还是原来的值。
2.QString
QString采用隐式共享技术,将深拷贝和浅拷贝很好地结合了起来。
QString str1 = "ubuntu";
QString str2 = str1; // 发生一次浅拷贝 str1 str2指向同一个数据结构,引用计数器值为2
qDebug() << "str1 = " << str1; // str1 = "ubuntu"
qDebug() << "str2 = " << str2; // str2 = "ubuntu"
// 对 QString对象str2修改导致一次深拷贝,str2指向一个新的与str1指向不同的数据结构其引用计数值为1,str1指向的数据结构引用计数器值减1后为1
str2[2] = 'm';
str2[0] = 'o';
qDebug() << "str1 = " << str1; // str1 = "ubuntu"
qDebug() << "str2 = " << str2; // str2 = "obmntu"
// str2赋值给str1,str1原指向的数据结构的引用数修改为0,也就是没有对象使用这个数据结构会从内存中释放
// 操作结束后,str1 str2 指向同一个数据结构,引用计数器值为2
str1 = str2;
qDebug() << "str1 = " << str1; // str1 = "obmntu"
qDebug() << "str2 = " << str2; // str2 = "obmntu"