欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

c++ 拷贝构造函数(重点在内含指针的浅拷贝和深拷贝)

程序员文章站 2023-10-28 22:16:22
今天同事问了一个关于拷贝构造函数的问题,类中包含指针的情况,今天就来说说c++的拷贝构造函数。 c++的拷贝构造函数是构造函数的一种,是对类对象的初始化,拷贝构造函数只有一个参数就是本类的引用。 注意,默认构造函数(即无参构造函数)不一定存在,但是拷贝构造函数总是会存在。 下面是一个拷贝构造函数的例 ......

今天同事问了一个关于拷贝构造函数的问题,类中包含指针的情况,今天就来说说c++的拷贝构造函数。

c++的拷贝构造函数是构造函数的一种,是对类对象的初始化,拷贝构造函数只有一个参数就是本类的引用。

注意,默认构造函数(即无参构造函数)不一定存在,但是拷贝构造函数总是会存在。

下面是一个拷贝构造函数的例子。

 1 #include<iostream>
 2 using namespace std;
 3 class a{
 4 public:
 5     int a;
 6     a(int value){
 7         a = value;
 8     }
 9     void show(){
10         cout<<a<<endl;
11     }
12 }; 
13 int main(){
14     a test_a(10);
15     test_a.show();
16     
17     a test_b(test_a);
18     test_b.show();
19     
20     return 0;
21 }

输出结果为:

10
10

如果编写了拷贝构造函数,则默认拷贝构造函数就不存在了。下面是一个非默认拷贝构造函数的例子。

 1 #include<iostream>
 2 using namespace std;
 3 class a{
 4 public:
 5     int a;
 6     a(int value){
 7         a = value;
 8     }
 9     a(a& tmp){
10         a = tmp.a;
11         cout<<"call copy construct"<<endl;
12     }
13     void show(){
14         cout<<a<<endl;
15     }
16 }; 
17 int main(){
18     a test_a(10);
19     test_a.show();
20     
21     a test_b(test_a);
22     test_b.show();
23     
24     return 0;
25 }

输出结果为:

10
call copy construct
10

拷贝构造函数被调用的三种情况

拷贝构造函数在以下三种情况下会被调用。

1) 当用一个对象去初始化同类的另一个对象时,会引发拷贝构造函数被调用。例如,下面的两条语句都会引发拷贝构造函数的调用,用以初始化 test_b。

1 a test_b(test_a);
2 a test_b = test_a;

这两条语句是等价的。

注意,第二条语句是初始化语句,不是赋值语句。赋值语句的等号左边是一个早已有定义的变量,赋值语句不会引发拷贝构造函数的调用。例如:

1 a test_a,test_b;
2 test_b = test_a;

这条语句不会引发拷贝构造函数的调用,因为  test_b  早已生成,已经初始化过了。

2) 如果函数 f 的参数是类 a 的对象,那么当 f 被调用时,类 a 的拷贝构造函数将被调用。换句话说,作为形参的对象,是用复制构造函数初始化的,而且调用拷贝构造函数时的参数,就是调用函数时所给的实参。

3) 如果函数的返冋值是类 a 的对象,则函数返冋时,类 a 的拷贝构造函数被调用。换言之,作为函数返回值的对象是用拷贝构造函数初始化 的,而调用拷贝构造函数时的实参,就是 return 语句所返回的对象。例如下面的程序:

 1 #include<iostream>
 2 using namespace std;
 3 class a{
 4 public:
 5     int a;
 6     a(int value){
 7         a = value;
 8     }
 9     a(a& tmp){
10         a = tmp.a;
11         cout<<"call copy construct"<<endl;
12     }
13     void show(){
14         cout<<a<<endl;
15     }
16 }; 
17 a func() {
18     a test_a(4);
19     return test_a;
20 }
21 int main(){
22     func().show(); 
23     
24     return 0;
25 }

输出结果:

call copy construct
4

针对于第三条,有些编译器可能会有以下的结果:

4

这是因为编译器编译的时候进行了优化,函数返回值对象就不用拷贝构造函数初始化了,这其实并不符合 c++的标准。

浅拷贝和深拷贝

重头戏来了,内含指针的拷贝构造函数,c++是如何实现的呢,来看个例子:

 1 #include<iostream>
 2 using namespace std;
 3 class a{
 4 public:
 5         int a;
 6         int *p;
 7         a(int value1, int value2){
 8                 a = value1;
 9                 p = new int(value2);
10         }
11         ~a(){
12                 delete p;
13         }
14 
15         void show(){
16                 cout<<a<<endl;
17                 cout<<p<<endl;
18                 cout<<*p<<endl;
19         }
20 };
21 
22 int main(){
23         a test_a(10,20);
24         test_a.show();
25 
26         a test_b(test_a);
27         test_b.show();
28 
29         return 0;
30 }

输出结果如下:

10
0xf19010
20
10
0xf19010
20
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000000f19010 ***
...

可以看到对于class a 的对象 test_a 和 test_b 指针p 指向了同一块内存,在对象析构的时候被析构了两次导致了crash,这就是我们常说的浅拷贝。

因此,在我们日常编写代码的时候特别需要注意这一点,对于指针我们需要相应的开辟一块新的内存,将指向的值拷贝过来,也就是所谓的深拷贝,下面是正确的写法:

 1 #include<iostream>
 2 using namespace std;
 3 class a{
 4 public:
 5     int a;
 6     int *p;
 7     a(int value1, int value2){
 8         a = value;
 9         p = new int(value2);
10     }
11     a(a& tmp){
12         a = tmp.a;
13         p = new int(* tmp.p);
14     }
15     ~a(){
16         delete p;
17     }
18 
19     void show(){
20         cout<<a<<endl;
21         cout<<p<<endl;
22         cout<<*p<<endl;
23     }
24 }; 
25 
26 int main(){
27     a test_a(10,20); 
28     test_a.show();
29     
30     a test_b(test_a);
31     test_b.show();
32     
33     return 0;
34 }

输出结果如下:

10
0xd4d010
20
10
0xd4d030
20