经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
拷贝构造器(深拷贝与浅拷贝)
来源:cnblogs  作者:bruce628  时间:2021/5/10 17:52:52  对本文有异议

一. 概述

复习巩固学习过的知识C++拷贝构造器。

环境:Centos7 64位,g++ 4.8.5

 

二. 代码与验证

1. 构造与拷贝构造

拷贝构造器(copy constructor)的地位与构造器(constructor)的地位是一样的,都是由无到有的创建过程。拷贝构造器,是由同类对象创建新对象的过程。

通过下面的代码验证几种情况。类A中自实现了构造器,拷贝构造器,析构器。

第28行、第32行代码调用 了构造函数,第29行代码调用了拷贝构造函数,这3行代码比较好理解。

第30行,调用了拷贝构造函数,一时有点不好理解,感觉有点像是调用了赋值运算符函数。但是通过运行结果,可以看到它确实是调用了拷贝构造函数。

可以再回顾一下上面的这句话“由同类对象创建新对象”,可能会更好地帮助理解。

  1. 1 #include <iostream>
  2. 2
  3. 3 using namespace std;
  4. 4
  5. 5 class A
  6. 6 {
  7. 7 public:
  8. 8 A()
  9. 9 {
  10. 10 cout<<"constructor A()"<<endl;
  11. 11 }
  12. 12
  13. 13 A(const A &another)
  14. 14 {
  15. 15 cout<<"A(const A &another)"<<endl;
  16. 16 }
  17. 17
  18. 18 ~A()
  19. 19 {
  20. 20 cout<<"~A()"<<endl;
  21. 21 }
  22. 22 protected:
  23. 23 int m_a;
  24. 24 };
  25. 25
  26. 26 int main()
  27. 27 {
  28. 28 A a1; // constructor 构造
  29. 29 A a2(a1); // copy constructor 拷贝构造
  30. 30 A a3 = a1; // copy constructor 拷贝构造
  31. 31
  32. 32 A a4; // constructor 构造
  33. 33 a4 = a1; // assign
  34. 34
  35. 35 return 0;
  36. 36 }

运行结果如下:

 再结合下面代码以进一步理解一下

  1. 1 int a = 0; // 初始化
  2. 2 a = 10; // 赋值
  3. 3 int b = a; // 初始化

 

2. 验证,不自实现拷贝构造器时会发生什么

注释掉类A中的自实现的拷贝构造函数第15行--第18行代码。

通过运行结果,可看到对象a1与a2调用dis()方法,两者打印结果一致,说明第40行代码的确是执行了拷贝构造。

注:此时是有系统提供的默认的拷贝构造器。

  1. 1 #include <iostream>
  2. 2
  3. 3 using namespace std;
  4. 4
  5. 5 class A
  6. 6 {
  7. 7 public:
  8. 8 A(int x = 10)
  9. 9 :m_a(x)
  10. 10 {
  11. 11 cout<<"constructor A()"<<endl;
  12. 12 }
  13. 13
  14. 14 /*
  15. 15 A(const A &another)
  16. 16 {
  17. 17 cout<<"A(const A &another)"<<endl;
  18. 18 }
  19. 19 */
  20. 20
  21. 21 ~A()
  22. 22 {
  23. 23 cout<<"~A()"<<endl;
  24. 24 }
  25. 25
  26. 26 void dis()
  27. 27 {
  28. 28 cout<<"m_a: "<<m_a<<endl;
  29. 29 }
  30. 30 protected:
  31. 31 int m_a;
  32. 32 };
  33. 33
  34. 34 int main()
  35. 35 {
  36. 36 A a1(42);
  37. 37 a1.dis();
  38. 38
  39. 39 cout<<"----------"<<endl;
  40. 40 A a2(a1);
  41. 41 a2.dis();
  42. 42
  43. 43 return 0;
  44. 44 }

运行结果如下:

 

3. 说明

1)系统提供了默认的拷贝构造器,拷贝的格式比较固定,一经自实现,默认的将不复存在;

2)此拷贝构造器不是空的,而是提供了一个等位拷贝机制。等位拷贝不包含成员函数;

3)系统提供的拷贝构造函数,是一种浅拷贝,shallow copy;

4)深拷贝,deep copy。如果对象中不含有堆上的空间(指针指向的堆上的空间),此时浅拷贝可以满足需求,不需要自实现。但如果对象中含有堆上的空间,此时浅拷贝不能满足需求,就需要自实现了(申请内存空间后再进行拷贝)。因为浅拷贝会带来重析构(double free)的问题。

 

拷贝构造格式,如下,another可以写成自己习惯的名称。

注:同类对象方法间,进行传参,可以访问其私有成员,其它则不行(同类间无私处,异类间有友元----老司机总结的结论)。

  1. 1 A(const A &another)
  2. 2 {
  3. 3 m_a = another.m_a;
  4. 4 }

 

4. 关于重析构double free的验证

上面第4)点,通过以下代码验证一下。类A中自实现了构造器和析构器。拷贝构造函数保持系统默认。构造函数中,给成员变量m_a申请了内存空间,并向其拷贝了字符串。类型最好转换下,不过这样也通过了编译。先不改了。

从运行结果可以看到double free的报错,报错的其它内容都看不太懂,先不管。

  1. 1 #include <iostream>
  2. 2 #include <cstring>
  3. 3
  4. 4 using namespace std;
  5. 5
  6. 6 class A
  7. 7 {
  8. 8 public:
  9. 9 A()
  10. 10 {
  11. 11 m_a = new char[100];
  12. 12 strcpy(m_a, "C++ is the intersting language.");
  13. 13 cout<<"constructor A()"<<endl;
  14. 14 }
  15. 15
  16. 16 ~A()
  17. 17 {
  18. 18 delete []m_a;
  19. 19 }
  20. 20
  21. 21 void dis()
  22. 22 {
  23. 23 cout<<"m_a: "<<m_a<<endl;
  24. 24 }
  25. 25 protected:
  26. 26 char *m_a;
  27. 27 };
  28. 28
  29. 29 int main()
  30. 30 {
  31. 31 A a1;
  32. 32 a1.dis();
  33. 33
  34. 34 cout<<"----------"<<endl;
  35. 35 A a2(a1);
  36. 36 a2.dis();
  37. 37
  38. 38 return 0;
  39. 39 }

运行结果如下:

  说明

对象a1、a2中的m_a指向了同一块堆空间,对象销毁,析构的时候析构了两次,所以报错double free。下面通过代码再验证一下。

 

5. 再次验证重析构

类A中增加setStr()方法,25-28行代码。第47行代码,对象a1调用setStr()方法修改对象a1中的成员变量m_a。第48行代码,a2调用dis()方法打印m_a。第50行增加了一个死循环,暂不让析构函数执行。

  1. 1 class A
  2. 2 {
  3. 3 public:
  4. 4 A()
  5. 5 {
  6. 6 m_a = new char[100];
  7. 7 strcpy(m_a, "C++ is the intersting language.");
  8. 8 cout<<"constructor A()"<<endl;
  9. 9 }
  10. 10
  11. 11 /*
  12. 12 A(const A &another)
  13. 13 {
  14. 14 m_a = new char[strlen(another.m_a)+1];
  15. 15 strcpy(m_a, another.m_a);
  16. 16 cout<<"A(const A &another)"<<endl;
  17. 17 }
  18. 18 */
  19. 19
  20. 20 ~A()
  21. 21 {
  22. 22 delete []m_a;
  23. 23 }
  24. 24
  25. 25 void setStr()
  26. 26 {
  27. 27 strcpy(m_a, "PHP is a intersting language.");
  28. 28 }
  29. 29
  30. 30 void dis()
  31. 31 {
  32. 32 cout<<"m_a: "<<m_a<<endl;
  33. 33 }
  34. 34 protected:
  35. 35 char *m_a;
  36. 36 };
  37. 37
  38. 38 int main()
  39. 39 {
  40. 40 A a1;
  41. 41 a1.dis();
  42. 42
  43. 43 cout<<"----------"<<endl;
  44. 44 A a2(a1);
  45. 45 a2.dis();
  46. 46
  47. 47 a1.setStr(); // modify m_a
  48. 48 a2.dis();
  49. 49
  50. 50 while(1);
  51. 51
  52. 52 return 0;
  53. 53 }

 运行结果如下:

 对象 a2中的m_a居然也被修改了。佐证了"对象a1、a2中的m_a指向了同一块堆空间"。拷贝构造,只是将a1中m_a的地址拷贝给了a2中的m_a,但两者均指向同一块堆空间。

 

6. 深拷贝。对于含有堆上的空间,自实现拷贝构造

将上面代码块的第12到17行代码去掉注释,

  1. 1 A(const A &another)
  2. 2 {
  3. 3 m_a = new char[strlen(another.m_a)+1];
  4. 4 strcpy(m_a, another.m_a);
  5. 5 cout<<"A(const A &another)"<<endl;
  6. 6 }

执行结果如下

 a2.dis(),打印是对象a2的成员变量m_a指向的重新申请的内存空间的内容。

此时,上面代码块注释掉第50行死循环的代码,运行也不会报错了。

现在,对象a1与a2中的m_a是不同的地址,并且它们指向不同的堆空间,但是它们的内容是一样的。调用析构函数时,分别free不同的m_a指向的各自的堆空间,也就不会有double free的问题了。

下面这张图可以帮助理解。这里的m_a其实就是图中的_str。

 

7. 拷贝构造器的适用场景

主要是用于传参和返回。

以下简单地说一下传参的问题

在上面代码块中,添加一个全局函数

  1. 1 void foo(A aa)
  2. 2 {
  3. 3 cout<<"void foo(A aa)"<<endl;
  4. 4 }

在main函数中,调用此全局函数时

  1. 1 int main()
  2. 2 {
  3. 3 A a1;
  4. 4 a1.dis();
  5. 5
  6. 6 cout<<"----------"<<endl;
  7. 7 foo(a1);
  8. 8
  9. 9 return 0;
  10. 10 }

运行结果如下:

 从结果可看到,在传参时,调用了拷贝构造函数。也就是,第7行代码,在调用foo(a1)函数时,将对象a1拷贝给了函数foo形参aa。这种传参方式,调用了拷贝构造函数。

如果我们换成引用试试,即将全局函数的形参修改为A &aa,即为void foo(A &aa)。运行结果如下:

 此时,没有调用拷贝构造函数。传引用,其实也就是在传递对象本身,也不会涉及到拷贝的问题了。在这种情形下,传引用比传对象开销要相对小点。

 

暂时就啰嗦这么多了。

 

 参考材料:

《C++基础与提高》  王桂林

 

原文链接:http://www.cnblogs.com/zhe666/p/14749374.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号