一、为什么会出现深拷贝和浅拷贝
在我们没有自定义拷贝构造函数的时候,传递对象给函数或者函数返回对象都能进行是因为C++编译器会给我们自动的产生一个拷贝构造函数,这就是“默认拷贝构造函数”,这个构造函数很简单,仅仅是使用“老对象”的值对“新对象”的数据成员依次进行赋值操作。
二、浅拷贝问题抛出
2.1 问题描述及代码
在不写拷贝构造函数的时候,使用C++编译器的默认拷贝构造函数运行下面代码,会出现跑断的现象。就是代码会自动断点进入析构函数那一块。
#include<iostream>
using namespace std;
class Name {
public:
//构造函数,p指向堆中分配的一块内存
Name(const char*_p) {
len = strlen(_p) + 1; //要加上结束字符
p = (char*) malloc(len);
strcpy_s(p, len, _p);
}
//析构函数,释放动态分配的内存
~Name(){
if (p!=NULL) {
free(p);
p = NULL;
len = 0;
}
}
private:
char *p;
int len;//定义指针指向的内存空间有多长
};
void objplay() {
Name obj1("abcdefg");//调用有参数构造函数初始化
Name obj2 = obj1; //调用拷贝构造函数,用obj1去初始化obj2
//在对象析构的时候断掉了,析构过程中先析构obj2就把内存空间析构了,这样obj1就指向了垃圾的内存空
//间。再析构obj1的时候就找不到内存了
//失败,浅拷贝。动态分配的内存消失等于出现了个野指针。内存泄漏!!
}
int main()
{
objplay();
cout << "finish!" << endl;
system("pause");
return 0;
}
2.2 问题解析
出现这种原因是因为默认的拷贝构造函数为浅拷贝。所谓浅拷贝,指的是在对象复制时,只是对象中的数据成员进行简单的赋值。浅拷贝,只是把指针变量的值拷贝过来了,并没有重新开辟内存空间(不要忘记全局区也存放着一个常量字符串!!)
析构的过程中:先析构obj2就把内存空间析构了,这样obj1就指向了垃圾的内存空间。再析构obj1的时候就找不到内存了
所以动态分配的内存消失等于出现了个野指针。内存泄漏!!
两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用“深拷贝”。
三、用深拷贝解决问题
3.1 深拷贝
在深拷贝的情况下,对于对象中动态成员就不能仅仅简单地赋值了,而应该重新动态分配空间,所以上诉例子改为深拷贝如下:
#include<iostream>
using namespace std;
class Name {
public:
Name(const char*_p) {
len = strlen(_p) + 1; //要加上结束字符
p = (char*) malloc(len);
strcpy_s(p, len, _p);
}
~Name(){
if (p!=NULL) {
free(p);
p = NULL;
len = 0;
}
}
//解决方案,手工的编写拷贝构造函数使用深拷贝
//显示的写一个拷贝构造函数,不用C++编译器默认的拷贝构造函数,这样就解决了浅拷贝的问题。
//手写拷贝构造函数深拷贝
Name(const Name& obj1) {
int len = obj1.len;
p = (char*)malloc(len); //重新分配内存空间!!
strcpy_s(p, len, obj1.p);
}
private:
char *p;
int len;//定义指针指向的内存空间有多长
};
void objplay() {
Name obj1("abcdefg");//内存地址
Name obj2 = obj1;
Name obj3("obj3");
//此时需要重载等号操作符
obj3 = obj1;//等号操作:C++把对象1的属性拷贝成对象3的属性(也是浅拷贝!!)
}
int main()
{
objplay();
cout << "finish!" << endl;
system("pause");
return 0;
}
3.2 图示分析
此时析构的时候就不会出现野指针,图示蓝色区域。obj2在初始化的过程中是重新开辟内存空间,这样析构的过程中就不会出现因为析构两次而出现野指针的状态!
特别注意:
obj3 = obj1; //等号操作:C++把对象1的属性拷贝成对象3的属性(也是浅拷贝!!)
//此时需要重载等号操作符
导致内存泄漏 又会出现断点。需要重载等号操作符
四、总结
防止默认拷贝发生!
技巧: 通过对对象复制的分析,我们发现对象的复制大多在进行“值传递”时发生,这里有一个小技巧可以防止按值传递——声明一个私有拷贝构造函数。甚至不必去定义这个拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类对象,将得到一个编译错误,从而可以避免按值传递或返回对象。
// 防止按值传递
class CExample
{
private:
int a;
public:
//构造函数
CExample(int b)
{
a = b;
cout<<"creat: "<<a<<endl;
}
private:
//拷贝构造,只是声明
CExample(const CExample& C);
public:
~CExample()
{
cout<< "delete: "<<a<<endl;
}
void Show ()
{
cout<<a<<endl;
}
};
//全局函数
void g_Fun(CExample C)
{
cout<<"test"<<endl;
}
int main()
{
CExample test(1);
//g_Fun(test); 按值传递将出错
return 0;
}