拷贝构造函数,默认拷贝构造函数
1.c++的默认拷贝构造函数,从深度拷贝和浅拷贝说起
- c++类的默认拷贝构造函数的弊端
c++类的中有两个特殊的构造函数,(1)无参构造函数,(2)拷贝构造函数。它们的特殊之处在于:
(1)当类中没有定义任何构造函数时,编译器会默认提供一个无参构造函数且其函数体为空;
(2)当类中没有定义拷贝构造函数时,编译器会默认提供一个拷贝构造函数,进行成员变量之间的拷贝。(这个拷贝操作是浅拷贝)
class TestCls{
public:
int a;
int *p;
public:
TestCls() //无参构造函数
{
std::cout<<“TestCls()”<<std::endl;
p = new int;
}
~TestCls() //析构函数
{
delete p;
std::cout<<"~TestCls()"<<std::endl;
}
};
int main(void)
{
TestCls t;
return 0;
}
编译运行确实不会出错:
类在我们没有定义拷贝构造函数的时候,会默认定义默认拷贝构造函数,也就是说可以直接用同类型的类间可以相互赋值、初始化:
int main(void)
{ TestCls t1; TestCls t2 = t1; //效果等同于TestCls t2(t1); return 0; }
这样结果就会出错。
原因就在于,默认的拷贝构造函数实现的是浅拷贝。
2.深度拷贝和浅拷贝
深度拷贝和浅拷贝在c语言中就经常遇到的了,在这里我简单描述。
一般的赋值操作是深度拷贝:
//深度拷贝
int a = 5;
int b = a;
简单的指针指向,则是浅拷贝:
//浅拷贝
int a = 8;
int *p;
p = &a;
char* str1 = “HelloWorld”;
char* str2 = str1;
将上面的浅拷贝改为深度拷贝后:
//深度拷贝
int a = 8;
int *p = new int;
*p = a;
char* str1 = “HelloWorld”;
int len = strlen(str1);
char *str2 = new char[len];
memcpy(str2, str1, len);
能看得出深度拷贝和浅拷贝的差异。拷贝者和被拷贝者若是同一个地址,则为浅拷贝,反之为深拷贝。
以字符串拷贝为例,浅拷贝后,str1和str2同指向0x123456,不管哪一个指针,对该空间内容的修改都会影响另一个指针。
深拷贝后,str1和str2指向不同的内存空间,各自的空间的内容一样。因为空间不同,所以不管哪一个指针,对该空间内容的修改都不会影响另一个指针。
- 解决默认拷贝构造函数的弊端
类的默认拷贝构造函数只会用被拷贝类的成员的值为拷贝类简单初始化,也就是说二者的p指针指向的内存空间是一致的。以前面TestCls可以知道,编译器为我们默认定义的拷贝构造函数为:
TestCls(const TestCls& testCls)
{ a = testCls.a; p = testCls.p; //两个类的p指针指向的地址一致。 }
main函数将要退出时,拷贝类t2的析构函数先得到执行,它把自身p指向的堆空间释放了;接下来,t1的析构函数得到调用,被拷贝类t1的析构函数得到调用,它同样要去析构自身的p指向指向的堆空间,但是该空间和t2类中p指向的空间一样,造成重复释放,程序运行崩溃。
解决办法就是自定义拷贝构造函数;
class TestCls{
public:
int a;
int *p;
public:
TestCls()
{
std::cout<<“TestCls()”<<std::endl;
p = new int;
}
TestCls(const TestCls& testCls)
{
cout<<"TestCls(const TestCls& testCls)"<endl;
a = testCls.a;
//p = testCls.p;
p = new int;
*p = *(testCls.p); //为拷贝类的p指针分配空间,实现深度拷贝
}
~TestCls()
{
delete p;
cout<<"~TestCls()"<<endl;
}
};
int main(void)
{
TestCls t1;
TestCls t2 = t1;
return 0;
}