【C++】【复杂结构拷贝问题】深拷贝与引用类型成员以及常量成员

为什么要有深拷贝?

当类的成员中有引用类型,那么拷贝的时候,比如

1 class A{
2    public3       int * addr;
4 }
5 A a;
6 A a1(a);
7 //此时 a和a1中的addr成员,都将同时引用同一块内存,那么当析构的时候,就会出现free after free的错误

所以需要我们自己实现一个深拷贝,比如

1 A(const A & a){
2   *addr = new int (*a.addr); 
3 }

此时新构造出a1对象,其成员 addr指针就指向了属于自己的一块内存,并把a的addr所指向内存的值给拷贝了过来

引用类型和常量类型的初始化

如果一个类是这样定义的:

1 Class A
2 {
3      public:
4           A(int pram1, int pram2, int pram3);
5      privite:
6           int a;
7           int &b;
8           const int c; 
9 }

假如在构造函数中对三个私有变量进行赋值则通常会这样写:

1 A::A(int pram1, int pram2, int pram3)
2 {
3      a=pram1;
4      b=pram2;
5      c=pram3;
6 }

但是,这样是编译不过的。因为常量和引用初始化必须赋值。所以上面的构造函数的写法只是简单的赋值,并不是初始化。

正确写法应该是:

1 A::A(int pram1, int pram2, int pram3):b(pram2),c(pram3)
2 {
3      a=pram1;
4 }

采用初始化列表实现了对常量和引用的初始化。采用括号赋值的方法,括号赋值只能用在变量的初始化而不能用在定义之后的赋值。

凡是有引用类型的成员变量或者常量类型的变量的类,不能有缺省构造函数默认构造函数没有对引用成员提供默认的初始化机制,也因此造成引用未初始化的编译错误。并且必须使用初始化列表进行初始化const对象、引用对象

当成员出现const以及引用类型,拷贝构造的情形

先看看引用类型是如何构造和拷贝构造的,贴出代码如下,

 1 #include <iostream>
 2 using namespace std;
 3 class A{
 4     public:
 5     int & b;
 6     int c;
 7     A(int & c1):b(c1){
 8         this->c=c1;
 9         cout << this->b <<endl;
10         cout << "普通构造"<<endl;
11     }
12     A(A & a):b(a.b){
13         cout << a.b<< endl;
14         c=a.c;
15         cout << a.b<< endl;
16         cout << "拷贝构造"<<endl;
17     }    
18 };
19 
20 int main()
21 {
22     int r=2;
23     // int a=2;效果也一样
24     int &a=r;
25     A a1(a);
26     
27     A a2(a1);
28     cout << a1.b << ' ' <<a1.c<< ' '<<a2.b<<' '<< a2.c << endl;
29     
30    return 0;
31 }

说明:此时a1和a2两个对象的引用成员b,实际上都是变量r的引用(这一点可以通过在code中修改r看看打印从而验证,这里就不重复贴代码了),可以看到在拷贝构造中,也是用列表进行初始化的

这里要注意一点:在普通构造中,类中如果有引用成员,而且希望这个引用成员绑定一个类外的变量,那么普通构造也应该用引用传参,否则如果用值传递,就会绑定一个将亡值,从而绑定一块没有意义的内存。如果希望这个引用成员绑定一个类内的成员变量,那么用值传递先初始化这个成员变量,再绑定即可。

 接下来看一下引用类型的拷贝构造会不会有深浅拷贝的问题,也就是在析构的时候重复释放内存的问题,

 1 #include <iostream>
 2 #include <stdlib.h>
 3 using namespace std;
 4 class A{
 5     public:
 6     int* & b;
 7     int c;
 8     int *p;
 9     A(int *c1):b(c1),c(*c1){
10         this->p=new int(*c1);
11         cout << p<<endl;
12         cout << "普通构造"<<endl;
13     }
14     A(A & a):b(a.b),c(a.c){
15         this->p= new int(*a.p);
16         cout << p<<endl;
17         cout << "拷贝构造"<<endl;        
18     }
19     ~A(){
20         delete this->p;
21         this->p=nullptr;
22     }
23 };
24 int main()
25 {
26     int a=4;
27     cout << &a <<endl;
28     {
29     A t1(&a);    
30     A t2(t1);
31     }        
32    return 0;
33 }

如上图代码所示,如果拷贝构造中不另外申请空间,则会导致t1和t2的p指针成员指向同一块空间,从而导致重复释放,这就是深浅拷贝的问题

但同时我们也可以看到,t1和t2的引用成员,绑定的其实是同一块地址(打印一下就知道了),但是在释放对象的时候并没有出现重复释放的问题,所以看来引用还是要比指针安全得多(详情见 引用与指针的区别 一篇)

posted on 2022-08-30 15:32  甲鱼写代码  阅读(150)  评论(0)    收藏  举报

导航