C++中的深拷贝和浅拷贝 QT中的深拷贝,浅拷贝和隐式共享

时间:2023-01-19 19:48:31
  • 下面是C++中定义的深,浅拷贝
    • 当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
      (1)一个对象以值传递的方式传入函数体 
      (2)一个对象以值传递的方式从函数返回 
      (3)一个对象需要通过另外一个对象进行初始化。

      如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,后面将进行说明。

      自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。

      浅拷贝和深拷贝

        在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

        深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。下面举个深拷贝的例子。


    • 总结:有资源的重新分配:深拷贝;无:浅拷贝(直接的调用默认的构造函数实际上也是共享了一款数据的内存,两个对象都指向这块数据内存)
  • 下面是QT中的深浅拷贝,我觉得它的东西理解起来比较实用
    •   

      1.浅拷贝:

         浅拷贝就比如像引用类型

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

      2.深拷贝:

         而深拷贝就比如值类型。改变了数据的内存指向,内存分配发生改变。

      Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等。

      3.隐式共享:

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

    • 实例:
      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 = 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()地址不是一回事。