【C++】【复杂结构拷贝问题】深拷贝与引用类型成员以及常量成员
为什么要有深拷贝?
当类的成员中有引用类型,那么拷贝的时候,比如
1 class A{ 2 public: 3 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的引用成员,绑定的其实是同一块地址(打印一下就知道了),但是在释放对象的时候并没有出现重复释放的问题,所以看来引用还是要比指针安全得多(详情见 引用与指针的区别 一篇)
浙公网安备 33010602011771号