构建自己的复制构造函数
上一篇文章简单地介绍了类的构造函数和析构函数,这里我们讨论一下,什么情况下我们必须定义自己的复制构造函数。同默认构造函数一样,如果我们没有定义复制构造函数,编译器在编译时会为我们自动生成一个复制构造函数。但是在有些情况下,编译器自动生成的复制构造函数会产生很严重的问题。读者现在可能还不知道,我们看个简单的例子。
1: #include <iostream>2: using namespace std;3:4: class Test
5: {6: public:
7: //重载构造函数
8: Test(char* p)
9: {10: cout<<"This is a override constructor."<<endl;
11: if(p)
12: {13: a=new char[strlen(p)+1];14: strcpy(a,p);15: }else
16: {17: a=new char[1];18: *a='\0';19: }20: }21: //析构函数
22: ~Test()23: {24: delete []a;
25: }26:27: public:
28: char* a;
29: };30:31: int main()
32: {33: Test *t1=new Test("Hello, C++!");34: Test t2(*t1);35: cout<<"t2.a="<<t2.a<<endl;
36:37: delete t1;
38: cout<<"t2.a="<<t2.a<<endl;
39: return 0;
40: }
有条件的朋友试一试,看看会得到什么结果。VS2010下的结果如下图所示。
为什么会这样呢?待我详细解释。编译器自动生成的复制构造函数知识把t1.a的指针简单地赋值给t2.a,也就意味着t1.a和t2.a所指向的是同一个地址。当执行完delete t1;后t1.a所指向的内存被释放掉了,t2.a就变成了野指针。野指针是造成程序崩溃、异常、不可预知错误的罪魁祸首。为了避免野指针的出现,在类含有指针数据成员的时候,我们必须构建自己的复制构造函数,避免指针的值拷贝。添加上自己的负责构造函数后,代码如下:
1: #include <iostream>2: using namespace std;3:4: class Test
5: {6: public:
7: //重载构造函数
8: Test(char* p)
9: {10: cout<<"This is a override constructor."<<endl;
11: if(p)
12: {13: a=new char[strlen(p)+1];14: strcpy(a,p);15: }else
16: {17: a=new char[1];18: *a='\0';19: }20: }21: //重载复制构造函数
22: Test(Test const &t)
23: {24: if (t.a)
25: {26: a=new char[strlen(t.a)+1];27: strcpy(a,t.a);28: }else
29: {30: a=new char[1];31: *a='\0';32: }33: }34: //析构函数
35: ~Test()36: {37: delete []a;
38: }39:40: public:
41: char* a;
42: };43:44: int main()
45: {46: Test *t1=new Test("Hello, C++!");47: Test t2(*t1);48: cout<<"t2.a="<<t2.a<<endl;
49:50: delete t1;
51: cout<<"t2.a="<<t2.a<<endl;
52: return 0;
53: }54:55:
这时候的运行结果才是我们需要的结果,结果如下图所示。
我想现在各位应该知道什么时候我们需要定义自己的复制构造函数了吧。好了,本文就写到这了。欢迎阅读《类的构造与析构(二)》,如有问题,欢迎指正。
感谢:《C++编程关键路径——程序员求职指南》