码迷,mamicode.com
首页 > 其他好文 > 详细

浅拷贝,深拷贝,隐式共享

时间:2015-07-30 23:14:09      阅读:265      评论:0      收藏:0      [点我收藏+]

标签:

1.浅拷贝:

   浅拷贝就比如像引用类型

   浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。举个例子,一个人一开始叫张三,后来改名叫李四了,可是还是同一个人,不管是张三缺胳膊少腿还是李四缺胳膊少腿,都是这个人倒霉。

 

2.深拷贝:

   而深拷贝就比如值类型。

  深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等。

3.隐式共享:

  隐式共享又叫做回写复制。当两个对象共享同一份数据时(通过浅拷贝实现数据块的共享),如果数据不改变,不进行数据的复制。而当某个对象需要改变数据时则执行深拷贝。

  QString类采用隐式共享技术,将深拷贝和浅拷贝有机地结合起来。

  例如:

void MainWindow::on_pushButton_8_clicked()
{
    QString str1="data";
    qDebug() << " String addr = " << &str1 <<", "<< str1.constData();
    QString str2=str1;  //浅拷贝指向同一个数据块
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData();
    str2[3]=e;       //一次深拷贝,str2对象指向一个新的、不同于str1所指向的数据结构
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData();
    str2[0]=f;       //不会引起任何形式的拷贝,因为str2指向的数据结构没有被共享
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData();
    str1=str2;         //str1指向的数据结构将会从内存释放掉,str1对象指向str2所指向的数据结构
    qDebug() << " String addr = " << &str1 <<", "<< str1.constData();
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData();
}

实测输出结果如下(括号内是我的分析):

String addr = 0x28c79c , 0x14316660 (str1的指针地址,指向一个新的QSharedDataPointer,命名为data1)
String addr = 0x28c798 , 0x14316660 (str2的指针地址,指向前面同一个QSharedDataPointer,命名为data1)
String addr = 0x28c798 , 0x1433f2a0 (str2的指针地址,指向一个新的QSharedDataPointer,命名为data2)
String addr = 0x28c798 , 0x1433f2a0 (str2的指针地址,指向data2,但是修改其内容)
String addr = 0x28c79c , 0x1433f2a0 (str1的指针地址,指向data2,不修改其内容,且放弃data1,使之引用计数为零而被彻底释放)
String addr = 0x28c798 , 0x1433f2a0 (str2的指针地址,指向data2,不修改其内容)

注意,str1的地址和str1.constData()地址不是一回事。

不过新问题又来了,在调用data()函数以后,怎么好像constData的地址也变了:

void MainWindow::on_pushButton_8_clicked()
{
    QString str1="data";
    qDebug() << " String addr = " << &str1 <<", "<< str1.constData() << ", " << str1.data();
    QString str2=str1;  //浅拷贝指向同一个数据块
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData() << ", " << str2.data();
    str2[3]=e;       //一次深拷贝,str2对象指向一个新的、不同于str1所指向的数据结构
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData() << ", " << str2.data();
    str2[0]=f;       //不会引起任何形式的拷贝,因为str2指向的数据结构没有被共享
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData() << ", " << str2.data();
    str1=str2;         //str1指向的数据结构将会从内存释放掉,str1对象指向str2所指向的数据结构
    qDebug() << " String addr = " << &str1 <<", "<< str1.constData() << ", " << str1.data();
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData() << ", " << str2.data();
}

输出结果:

String addr = 0x28c79c , 0x143e6660 , 0x143e6660
String addr = 0x28c798 , 0x14423020 , 0x14423020
String addr = 0x28c798 , 0x14423020 , 0x14423020
String addr = 0x28c798 , 0x14423020 , 0x14423020
String addr = 0x28c79c , 0x143e6660 , 0x143e6660
String addr = 0x28c798 , 0x14423020 , 0x14423020

原因可能是因为这两句:
1. constData()的注释:
Note that the pointer remains valid only as long as the string is not modified.
就是调用data()函数以后,string存储数据的地址被修改了
2. data()的注释:
Note that the pointer remains valid only as long as the string is not modified by other means.
For read-only access, constData() is faster because it never causes a deep copy to occur.
大概是因为调用data()函数以后,立刻就引起了深拷贝,从而存储数据的地址变化了

所以事实上,先调用constData还是先调用data,结果会有所不同:

void MainWindow::on_pushButton_8_clicked()
{
    QString str1="data";
    qDebug() << " String addr = " << &str1 <<", "<< str1.data() << ", " << str1.constData();
    QString str2=str1;  //浅拷贝指向同一个数据块
    qDebug() << " String addr = " << &str2 <<", "<< str2.data() << ", " << str2.constData();
    str2[3]=e;       //一次深拷贝,str2对象指向一个新的、不同于str1所指向的数据结构
    qDebug() << " String addr = " << &str2 <<", "<< str2.data() << ", " << str2.constData();
    str2[0]=f;       //不会引起任何形式的拷贝,因为str2指向的数据结构没有被共享
    qDebug() << " String addr = " << &str2 <<", "<< str2.data() << ", " << str2.constData();
    str1=str2;         //str1指向的数据结构将会从内存释放掉,str1对象指向str2所指向的数据结构
    qDebug() << " String addr = " << &str1 <<", "<< str1.data() << ", " << str1.constData();
    qDebug() << " String addr = " << &str2 <<", "<< str2.data() << ", " << str2.constData();
}

结果(其中constData是想要的结果,值得研究的地方)。而data函数因为深拷贝的原因产生了一个数据的新地址,大概是拷贝到新的存储空间吧,而constData始终指向这个QString真正存储数据的地方

String addr = 0x28c79c , 0x144b3598 , 0x144b3598
String addr = 0x28c798 , 0x14503cc8 , 0x144b3598
String addr = 0x28c798 , 0x14503cc8 , 0x14503cc8
String addr = 0x28c798 , 0x14503cc8 , 0x14503cc8
String addr = 0x28c79c , 0x144b3598 , 0x14503cc8
String addr = 0x28c798 , 0x14503cc8 , 0x14503cc8

要是先调用constData,后调用data,结果这下constData和data又完全一致了:

String addr = 0x28c79c , 0x146b6c28 , 0x146b6c28
String addr = 0x28c798 , 0x14653498 , 0x14653498
String addr = 0x28c798 , 0x14653498 , 0x14653498
String addr = 0x28c798 , 0x14653498 , 0x14653498
String addr = 0x28c79c , 0x146b6c28 , 0x146b6c28
String addr = 0x28c798 , 0x14653498 , 0x14653498

之所以出现这种怪问题,想了半天,觉得是因为data()和constData()写在同一句语句里的原因,编译器吧全部值算出来以后,再进行打印,这样constData的值有时候就不准确了。所以最好分成两句:

void MainWindow::on_pushButton_8_clicked()
{
    QString str1="data";
    qDebug() << " String addr = " << &str1 <<", "<< str1.constData(); qDebug() << "new addr = " << str1.data();
    QString str2=str1;  //浅拷贝指向同一个数据块
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData(); qDebug() << "new addr = " << str2.data();
    str2[3]=e;       //一次深拷贝,str2对象指向一个新的、不同于str1所指向的数据结构
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData(); qDebug() << "new addr = " << str2.data();
    str2[0]=f;       //不会引起任何形式的拷贝,因为str2指向的数据结构没有被共享
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData(); qDebug() << "new addr = " << str2.data();
    str1=str2;         //str1指向的数据结构将会从内存释放掉,str1对象指向str2所指向的数据结构
    qDebug() << " String addr = " << &str1 <<", "<< str1.constData(); qDebug() << "new addr = " << str1.data();
    qDebug() << " String addr = " << &str2 <<", "<< str2.constData(); qDebug() << "new addr = " << str2.data();
}

输出结果(排版了一下,取消换行):

String addr = 0x28c70c , 0x13c06660 , new addr = 0x13c06660
String addr = 0x28c708 , 0x13c06660 , new addr = 0x13c841b8
String addr = 0x28c708 , 0x13c841b8 , new addr = 0x13c841b8
String addr = 0x28c708 , 0x13c841b8 , new addr = 0x13c841b8
String addr = 0x28c70c , 0x13c841b8 , new addr = 0x13c06660
String addr = 0x28c708 , 0x13c841b8 , new addr = 0x13c841b8

这样就又正确了,真是烦死人。后面还有没有坑不知道,今天就到这里为止吧。

参考:http://www.cnblogs.com/wiessharling/archive/2013/01/05/2845819.html

--------------------------------------------------------------------

 再来一个例子:

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... ...).

参考:http://blog.chinaunix.net/uid-27177626-id-3949985.html

--------------------------------------------------------------------

int main(int argc, char *argv[])
{
    QList<QString> list1;
    list1<<"test";
    QList<QString> list2=list1;

    qDebug()<<&list1.at(0);
    qDebug()<<&list2.at(0);
    //qDebug()<<&list1[0];      //[]运算
    //qDebug()<<&list2[0];      //[]运算
    list2<<"tests";

    qDebug()<<&list1.at(0);
    qDebug()<<&list2.at(0);

    QList<QString> list=copyOnWrite();
    qDebug()<<&list;
    qDebug()<<&list.at(0);
}

QList<QString> copyOnWrite()
{
    QList<QString> list;
    list<<"str1"<<"str2";
    ///...
    qDebug()<<&list;
    qDebug()<<&list.at(0);
    return list;
}

输出结果:

0x13df5e28
0x13df5e28
0x13df5e28
0x13d95fa0
0x28c79c
0x13d900c0
0x28c79c
0x13d900c0

1. 网上都说是copyOnWrite函数体内&list地址与主函数中&list地址是一样的,结果却是不一致的,但元素地址是一致的,难道错了(理论上,两个list自身的地址应该是不一样的,为什么会结果一样呢?难道是windows销毁前一个list后,凑巧又给后一个list重新分配了一模一样的地址?这与QList使用隐式共享有关系吗?不明白)
2. 使用[]运算,数据结构经过复制,不再隐式共享。(在只读的情况下,使用at()方法要比使用[]运算子效率高,因为省去了数据结构的复制成本)。

参考:http://blog.csdn.net/yestda/article/details/17893221

浅拷贝,深拷贝,隐式共享

标签:

原文地址:http://www.cnblogs.com/findumars/p/4690778.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!