c++ 拷贝构造函数

622 阅读5分钟
今天同事问了一个关于拷贝构造函数的问题,类中包含指针的情况,今天就来说说c++的拷贝构造函数。
c++的拷贝构造函数是构造函数的一种,是对类对象的初始化,拷贝构造函数只有一个参数就是本类的引用。
注意,默认构造函数(即无参构造函数)不一定存在,但是拷贝构造函数总是会存在。
下面是一个拷贝构造函数的例子。
[url=][/url]

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
}
[url=][/url]


输出结果为:
10
10

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

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
}
[url=][/url]


输出结果为:
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 语句所返回的对象。例如下面的程序:
[url=][/url]

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
}
[url=][/url]


输出结果:
call copy construct
4

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

这是因为编译器编译的时候进行了优化,函数返回值对象就不用拷贝构造函数初始化了,这其实并不符合 C++的标准。
浅拷贝和深拷贝
重头戏来了,内含指针的拷贝构造函数,C++是如何实现的呢,来看个例子:
[url=][/url]

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
}
[url=][/url]


输出结果如下:
[url=][/url]

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


可以看到对于class A 的对象 test_a 和 test_b 指针p 指向了同一块内存,在对象析构的时候被析构了两次导致了crash,这就是我们常说的浅拷贝。
因此,在我们日常编写代码的时候特别需要注意这一点,对于指针我们需要相应的开辟一块新的内存,将指向的值拷贝过来,也就是所谓的深拷贝,下面是正确的写法:
[url=][/url]

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
}
[url=][/url]


输出结果如下:
10
0xd4d010
20
10
0xd4d030
20