(1)
深拷贝
:
即就是生成对象的一个完整的复制品;
(2)浅拷贝:只是一个引用复制(比如仅仅复制指向共享数据的指针)。
(3)隐式共享:也叫做回写复制(copy on write)。
Qt中的很多C++类通过使用隐式数据共享来最大化资源的使用效率和最小化复制的资源耗费。将隐
式共享类作为参数传递不仅安全而且效率高,因为在这个过程中只有指向这个数据的指针被传递,
并且当且仅当有函数对这个数据进行写操作时,才会对该数据进行复制。
隐式共享可以降低对内存和CPU资源的使用,提高程序的运行效率。使用隐式共享能使得在函数中(eg. 参数、返回值)使用值传递更有效率。 QString采用隐式共享技术,将深拷贝和浅拷贝很好地结合了起来。
举例说明饮食共享是如何工作的:
- QString str1 = "ubuntu";
- QString str2 = str1;//str2 = "ubuntu"
- str2[2] = "m";//str2 = "ubmntu",str1 = "ubuntu"
- str2[0] = "o";//str2 = "obmntu",str1 = "ubuntu"
- str1 = str2;//str1 = "obmntu",
line1: 初始化一个内容为"ubuntu"的字符串;
line2: 将字符串对象str1赋值给另外一个字符串str2(由QString的拷贝构造函数完成str2的初始化)。
在对str2赋值的时候,会发生一次浅拷贝,导致两个QString对象都会指向同一个数据结构。该数据结构除了保存字符串“ubuntu”之外,还保存一个引用计数器,用来记录字符串数据的引用次数。此处,str1和str2都指向同一数据结构,所以此时引用计数器的值为2.
line3: 对str2做修改,将会导致一次深拷贝,使得对象str2指向一个新的、不同于str1所指的数据结构(该数据结构中引用计数器值为1,只有str2是指向该结构的),同时修改原来的、str1所指向的数据结构,设置它的引用计数器值为1(此时只有str1对象指向该结构);并在这个str2所指向的、新的数据结构上完成数据的修改。引用计数为1就意味着该数据没有被共享。
line4: 进一步对str2做修改,不过不会引起任何形式的拷贝,因为str2所指向的数据结构没有被共享。
line5: 将str2赋给str1.此时,str1修改它指向的数据结构的引用计数器的值位0,表示没有QString类的对象再使用这个数据结构了;因此str1指向的数据结构将会从从内存中释放掉;这一步操作的结构是QString对象str1和str2都指向了字符串为“obmntu”的数据结构,该结构的引用计数为2.
Qt中支持引用计数的类有很多(QByteArray, QBrush, QDir, QBitmap... ...).
再比如:
- QList<QString> list1;
- list1 << "helianthus";
- QList<QString> list2 = list1;
-
- cout << &list1[0] << endl;
- cout << &list2[0] << endl;
在这个例子中使用了[]运算子,list1和list2中的数据结构经过了复制,所以并不是共享的。因此最后显示的两个记忆体位置并不相同,但是使用了at()时的情况是一样的:
- QList<QString> list1;
- list1 << "x";
- QList<QString> list2 = list1;
- //QList::at(int i) const:Returns the item at index position i in the list. i must be a
- //valid index position in the list (i.e., 0 <= i < size())
- cout << &(list1.at(0)) << endl;
- cout << &(list2.at(0)) < < endl;
所以,在只读的情况下,使用at()方法要比使用[]运算子效率高,因为省去了数据结构的复制成本。
隐式共享之所以称为copy on write,也就是说只要容器中的数据结构内容发生了变化就不再共享,而要复制。就上一个例子而言,如果对list2做了修改,比如:
那么:
- cout << &(list1.at(0)) << endl;
- cout << &(list2.at(0)) << endl;
此时list1和list2显示的记忆体位置就是不同的。
再比如:
- QList<int> function() {
- QList<int> list;
- // ...blah..blah
- cout << &list << endl;
- return list;
- }
然后,就可以利用这个函数实现数据共享,又不会数据结构复制:
- QList<int> retval= function();
- cout << &retval << endl;
在上面这个代码片段中,function()中的list位置和接收function中的list的retval
位置是相同的。总的来说,QT中所有的容器类都支持隐式共享;此外,QByteArray,QBrush,QPen,QPalette,QBitmap,QImage,QPixmap,QCursor,QDir,QFont和QVariant等也都只是隐式共享机制。
而无论是Java风格还是STL风格的迭代器,使用只读迭代器时,背后也都使用到了隐式共享机制,以增加读取的效率。