对深拷贝与浅拷贝的再次理解

记得11年底找工作的时候,面试时曾经遇到有面试官问的对深拷贝与浅拷贝的理解,那时候自己回来查了资料,写了篇博客,感觉自己理解了,其实理解的不深刻,最近在调试bug的时候,再次遇到深拷贝与浅拷贝,认真分析了,写写自己的心得吧。

先说下自己的理解吧,浅拷贝,即在定义一个类A,使用类似A obj;  A obj1(obj);或者A obj1 = obj; 时候,由于没有自定义拷贝构造函数,C++编译器自动会产生一个默认的拷贝构造函数。这个默认的拷贝构造函数采用的是“位拷贝”(浅拷贝),而非“值拷贝”(深拷贝)的方式,如果类中含有指针变量,默认的拷贝构造函数必定出错。

用一句简单的话来说就是浅拷贝,只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

浅拷贝会出现什么问题呢?

假如有一个成员变量的指针,char *m_data;

其一,浅拷贝只是拷贝了指针,使得两个指针指向同一个地址,这样在对象块结束,调用函数析构的时,会造成同一份资源析构2次,即delete同一块内存2次,造成程序崩溃。

其二,浅拷贝使得obj.m_data和obj1.m_data指向同一块内存,任何一方的变动都会影响到另一方。

其三,在释放内存的时候,会造成obj1.m_data原有的内存没有被释放(这句话,刚开始我不太理解,如果没有走自定义的拷贝构造函数,申请内存空间,A obj1(obj);也不走默认构造函数,走的是默认的拷贝构造函数,何来分配空间直说,更不会造成obj1.m_data原有的内存没有被释放,这里刚开始我一直有疑问),造成内存泄露。

事实是这样的,当delete obj.m_data, obj.m_data内存被释放后,由于之前obj.m_data和obj1.m_data指向的是同一个内存空间,obj1.m_data所指的空间不能在被利用了,delete obj1.m_data也不会成功,一致已经无法操作该空间,所以导致内存泄露。

深拷贝采用了在堆内存中申请新的空间来存储数据,这样每个可以避免指针悬挂。

下面来看看类string的拷贝构造函数

[cpp] view plain copy
  1.   class String
  2.   {
  3.       public:
  4.           String(const String &other);    //拷贝构造函数
  5.       private:
  6.           char *m_data;   //用于保存字符串
  7.   };
  8.   
  9.   String(const String &other)
  10.   {
  11.       int length = strlen(other.m_data);
  12.       m_data = new char[length + 1];
  13.       strcpy(m_data, other.m_data);
  14. }

可以看到在拷贝构造函数中为成员变量申请了新的内存空间,这就使得两个对象的成员变量不指向同一个内存空间,除非你的确需要这样做,用于实现一些其他的用途。

浅拷贝:也就是在对象复制时,只是对对象中的数据成员进行简单的赋值,如果对象中存在动态成员,即指针,浅拷贝就会出现问题,下面代码:

[cpp] view plain copy
  1. #include <stdio.h>
  2. class A
  3. {
  4. public:
  5. A()      // 构造函数,p指向堆中分配的一空间
  6. {
  7. m_data = new char(100);
  8. printf("默认构造函数\n");
  9. }
  10. ~A()     // 析构函数,释放动态分配的空间
  11. {
  12. if(m_data != NULL)
  13. {
  14. delete m_data;
  15. m_data = NULL;
  16. printf("析构函数\n");
  17. }
  18. }
  19. private:
  20. char *m_data;     // 一指针成员
  21. };
  22. int main()
  23. {
  24. A a;
  25. A b(a);   // 复制对象
  26. return 0;
  27. }

运行结果:

*** glibc detected *** ./simple: double free or corruption (fasttop): 0x000000000c62a010 ***

分析:由于没有拷贝构造函数,走编译器默认的拷贝构造函数,A b(a); 进行对象析构时,会造成释放同一内存空间2次,导致内存泄露。

深拷贝:对于深拷贝,针对成员变量存在指针的情况,不仅仅是简单的指针赋值,而是重新分配内存空间,如下:

[cpp] view plain copy
  1. #include <stdio.h>
  2. #include <string>
  3. class A
  4. {
  5. public:
  6. A()      // 构造函数,p指向堆中分配的一空间
  7. {
  8. m_pdata = new char(100);
  9. printf("默认构造函数\n");
  10. }
  11. A(const A& r)
  12. {
  13. m_pdata = new char(100);    // 为新对象重新动态分配空间
  14. memcpy(m_pdata, r.m_pdata, strlen(r.m_pdata));
  15. printf("copy构造函数\n");
  16. }
  17. ~A()     // 析构函数,释放动态分配的空间
  18. {
  19. if(m_pdata != NULL)
  20. {
  21. delete m_pdata;
  22. printf("析构函数\n");
  23. }
  24. }
  25. private:
  26. char *m_pdata;     // 一指针成员
  27. };
  28. int main()
  29. {
  30. A a;
  31. A b(a);   // 复制对象
  32. return 0;
  33. }

下面是我在具体的应用中使用深拷贝的情况,现在把这个demo贴出来:

[cpp] view plain copy
  1. #include <iostream>
  2. #include <errno.h>
  3. #include <vector>
  4. #include <stdio.h>
  5. using namespace std;
  6. /*存储记录信息的结构体*/
  7. typedef struct _RECODER_VALUE_STRU
  8. {
  9. int Id;
  10. int Age;
  11. }RECODER_VALUE_STRU;
  12. class recorder
  13. {
  14. public:
  15. recorder()
  16. {
  17. m_stru_RecValue.Id = -1;
  18. m_stru_RecValue.Age = -1;
  19. m_pRecValue = &m_stru_RecValue;
  20. m_paddr = new char[100];
  21. memset(m_paddr,0x00 ,100);
  22. printf("默认 construct recorder->&m_stru_RecValue: %x,\t m_pRecValue: %x\t m_paddr: %x\n", &m_stru_RecValue, m_pRecVa
  23. lue, m_paddr);
  24. }
  25. //拷贝构造函数
  26. /*              recorder(const recorder &recorder)
  27. {
  28. m_stru_RecValue.Id = -1;
  29. m_stru_RecValue.Age = -1;
  30. m_stru_RecValue = recorder.m_stru_RecValue;
  31. m_pRecValue = &m_stru_RecValue;
  32. m_paddr = new char[100];
  33. memset(m_paddr, 0x00 ,100);
  34. memcpy(m_paddr, recorder.m_paddr, strlen(recorder.m_paddr));
  35. printf("拷贝 construct recorder->&m_stru_RecValue: %x\t m_pRecValue: %x\t m_paddr: %x\n",&m_stru_RecValue, m_pRecValu
  36. e, m_paddr);
  37. }
  38. */
  39. //构造函数
  40. recorder(int iId, int iAge)
  41. {
  42. m_stru_RecValue.Id = iId;
  43. m_stru_RecValue.Age = iAge;
  44. m_pRecValue = &m_stru_RecValue;
  45. m_paddr = new char[100];
  46. memset(m_paddr, 0x00 ,100);
  47. memcpy(m_paddr, &iAge, sizeof(int));
  48. printf("construct recorder->&m_stru_RecValue: %x \t m_pRecValue: %x\t m_paddr: %x\n", &m_stru_RecValue, m_pRecValue,
  49. m_paddr);
  50. }
  51. ~recorder()
  52. {
  53. //      cout<<"recorder 析构"<<endl;
  54. /*if(m_paddr != NULL)
  55. {
  56. delete m_paddr;
  57. m_paddr =NULL;
  58. }*/
  59. }
  60. public:
  61. RECODER_VALUE_STRU m_stru_RecValue;//存储记录信息的结构体
  62. void* m_pRecValue;//每条记录的值
  63. char *m_paddr;
  64. };
  65. int main()
  66. {
  67. cout <<"测试默认构造函数"<<endl<<endl;
  68. recorder btest;
  69. recorder btest1(btest);
  70. printf("非参:btest ->&m_stru_RecValue: %x\t addr: %x\t m_paddr: %x\n",  &btest.m_stru_RecValue, btest.m_pRecValue,  btest.m_paddr);
  71. printf("非参:btest1->&m_stru_RecValue: %x\t addr: %x\t m_paddr: %x\n", &btest1.m_stru_RecValue, btest1.m_pRecValue, btest1.m_paddr);
  72. cout << endl<<"测试带参数的构造函数"<<endl<<endl;
  73. recorder btest2(1, 100);
  74. recorder btest3(btest2);
  75. printf("带参:btest2->m_stru_RecValue: %x\t m_pRecValue: %x\t, m_paddr: %x\n", &btest2.m_stru_RecValue, btest2.m_pRecValue, btest2.m_
  76. paddr);
  77. printf("带参:btest3->m_stru_RecValue: %x\t m_pRecValue: %x\t, m_paddr: %x\n", &btest3.m_stru_RecValue, btest3.m_pRecValue, btest3.m_
  78. paddr);
  79. return 0;
  80. }

对比结果:

注释掉自定义拷贝构造函数,运行结果:

测试默认构造函数

默认 construct recorder->&m_stru_RecValue: ddbb8de0,     m_pRecValue: ddbb8de0   m_paddr: 1b8a0010

非参:btest ->&m_stru_RecValue: ddbb8de0         addr: ddbb8de0  m_paddr: 1b8a0010

非参:btest1->&m_stru_RecValue: ddbb8dc0         addr: ddbb8de0  m_paddr: 1b8a0010

测试带参数的构造函数

construct recorder->&m_stru_RecValue: ddbb8da0   m_pRecValue: ddbb8da0   m_paddr: 1b8a0080

带参:btest2->m_stru_RecValue: ddbb8da0  m_pRecValue: ddbb8da0  , m_paddr: 1b8a0080

带参:btest3->m_stru_RecValue: ddbb8d80  m_pRecValue: ddbb8da0  , m_paddr: 1b8a0080

默认拷贝构造函数结果分析:

通过结果可以看出,当成员变量为指针变量的时候,指针成员变量指向的地址都是同一个地址,无论是申请空间的成员变量m_pRecValue,和仅仅作为指针赋值的成员变量m_paddr;结构体的地址是变化的,除了指针浅拷贝与深拷贝没什么区别。

打开自定义拷贝构造函数,运行结果:

测试默认构造函数

默认 construct recorder->&m_stru_RecValue: 58bb9e20,     m_pRecValue: 58bb9e20   m_paddr: 7a2c010

拷贝 construct recorder->&m_stru_RecValue: 58bb9e00      m_pRecValue: 58bb9e00   m_paddr: 7a2c080

非参:btest ->&m_stru_RecValue: 58bb9e20         addr: 58bb9e20  m_paddr: 7a2c010

非参:btest1->&m_stru_RecValue: 58bb9e00         addr: 58bb9e00  m_paddr: 7a2c080

测试带参数的构造函数

construct recorder->&m_stru_RecValue: 58bb9de0   m_pRecValue: 58bb9de0   m_paddr: 7a2c0f0

拷贝 construct recorder->&m_stru_RecValue: 58bb9dc0      m_pRecValue: 58bb9dc0   m_paddr: 7a2c160

带参:btest2->m_stru_RecValue: 58bb9de0  m_pRecValue: 58bb9de0  , m_paddr: 7a2c0f0

带参:btest3->m_stru_RecValue: 58bb9dc0  m_pRecValue: 58bb9dc0  , m_paddr: 7a2c160

自定义深拷贝构造函数结果分析:

从结果可以看出,所有成员变量的地址都不相同。

其他:

1. 有时候为了防止默认拷贝发生,可以声明一个私有的拷贝构造函数(不用写代码),这样的话,如果试图调用 A  b(a); 就调用了私有的拷贝构造函数,编译器会报错,这也是一种偷懒的做法。

2.  一个类中可以存在多个拷贝构造函数,例如:

[cpp] view plain copy
  1. Calss A
  2. {
  3. Public:
  4. X(const X&);//const拷贝构造
  5. X(X &);//非const拷贝构造
  6. X(X& , int  iData);
  7. }

暂时就先分析到这里,如果以后遇到新的关于拷贝构造的情况,会继续分析。

如是转载,请指明原出处: http://blog.csdn.net/feitianxuxue,谢谢合作!

对深拷贝与浅拷贝的再次理解相关推荐

  1. 深拷贝与浅拷贝的简单理解

    什么是深拷贝与浅拷贝? 拷贝是什么? 拷贝(kǎobèi)是由英文copy的音译词,拷贝就是复制的意思,就是一个变量的值传给另外一个变量,就是拷贝. 浅拷贝和深拷贝的区别? 浅拷贝: 浅拷贝是指把存放 ...

  2. 深拷贝与浅拷贝~动态内存释放有关

    浅拷贝就是对象的数据成员之间的简单赋值,如你设计了一个没有类而没有提供它的复制构造函数,当用该类的一个对象去给令一个对象赋值时所执行的过程就是浅拷贝,如: class A{ public: A(int ...

  3. 深入浅出的“深拷贝与浅拷贝”

    js中的浅拷贝与深拷贝,只是针对复杂数据类型(object, Array)的复制问题.浅拷贝和深拷贝都可以实现在已有对象上再生出一份的作用.但是对象的实例是存储在堆内存中然后通过一个引用值只操作对象, ...

  4. C++拷贝构造函数、深拷贝、浅拷贝

    对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=88; int b=a;  而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. ...

  5. C++拷贝构造函数(深拷贝,浅拷贝)

    对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=88; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. ...

  6. C++拷贝构造函数(深拷贝与浅拷贝)

    对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=88; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. ...

  7. C++的深拷贝与浅拷贝

    对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=88; int b=a;  而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. ...

  8. C++ 类的深拷贝与浅拷贝||深拷贝通过重载拷贝构造函数与重载赋值运算符实现

    http://blog.csdn.net/wangshihui512/article/details/9842225 在面向对象程序设计中,对象间的相互拷贝和赋值是经常进行的操作. 如果对象在申明的同 ...

  9. C++深拷贝与浅拷贝的区别-简单易懂

    C++深拷贝与浅拷贝的区别-简单易懂 介绍 浅拷贝就比如像引用类型,而深拷贝就比如值类型. 浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同).对其中任何一个对象的改动都会影响另 ...

最新文章

  1. go int64转string_25. 深入学习 Go语言的反射
  2. php的文件路径问题
  3. Linux下调试python
  4. 访问数据库_访问数据库
  5. mysql数据库操作语句大全
  6. 南昌推进“互联网+”智慧城市建设
  7. java判断手机号_java使用正则表达式判断手机号的方法示例
  8. C/C++与内存相关的函数
  9. 【剑指offer】面试题40:最小的k个数(java)
  10. ssh-scan处理手记
  11. 单片机sleep函数的头文件_单片机空项目代码解析
  12. python io多路复用框架_python之IO多路复用
  13. vs2003远程调试方法
  14. 服务器移动用户打不开网站图片,为什么有些网站移动网络打不开?
  15. 基于SpringBoot+SpringSecurity+JWT+RSA加密算法签名 Auth权限认证搭建教程
  16. 从此甩掉光驱 U盘安装系统最详攻略(转自腾讯数码)
  17. 联通光纤猫hg8240路由功能开启
  18. 【c语言】判断整数x是否是同构数。若是同构数,函数返回1;否则返回0.
  19. Android 手机QQ临时会话
  20. JVM内存分哪几个区,每个区的作用是什么?

热门文章

  1. mybatis学习6复杂查询之一对多的处理
  2. 啊哈算法中的Dijkstra最短路算法(好理解!!!)
  3. 将csv文件导入到mysql数据库
  4. 《深度探索C++对象模型》--5 构造析构拷贝 6 执行期语意学
  5. ThreadLocal源码剖析
  6. 《JAVA与模式》之抽象工厂模式
  7. Java虚拟机学习(3): 类加载机制
  8. Oracle编程入门经典 第6章 在Oracle中处理语句
  9. 深度学习(二十七)可视化理解卷积神经网络-ECCV 2014
  10. 数字图像处理:第五章 代数运算