前言
在学习C++类与对象的拷贝构造函数和赋值运算符重载这两个成员函数,无法避免的需要考虑的是:深浅拷贝的话题。小编在这里会和大家一起谈到这个话题!
注:下面主要谈拷贝构造函数。赋值运算符重载以此类推!
1. 浅拷贝
浅拷贝: 就是以值的方式进行拷贝,对于默认的(编译器自己生成)拷贝构造函数来说,内置类型就是采用的是值拷贝的方式。来看这样一个示例,例1:
#include<iostream>
using namespace std;
class A
{
public:
A(int *ptr)
:_ptr(ptr)
{}
private:
int* _ptr;
};
int main()
{
int a = 1;
A a1(&a);
A a2(a1);
return 0;
}
这个结果是很合理的:采用了值拷贝的形式。将我们的a
的地址交给了A a2
对象。
这就是浅拷贝。
2. 深拷贝
关于上面的示例1,小编是故意使用的是指针类型。那么小编抛出以下问题:
-
指针是否可以指向一个数组?
-
指针是否可以指向一个堆区开辟的数组空间?
-
对于一个指针指向的数组其中的元素(或者其它申请的资源)而言,你希望它的拷贝是一种怎么样的拷贝?进一步来说:一个资源应该被多少个对象来管控?
我们将场景聚焦,例2:
#include<iostream>
using namespace std;
class Array
{
public:
Array(int size)
{
int* tmp = (int*)malloc(sizeof(int) * size);
if (tmp == nullptr)
{
perror("malloc");
return;
}
_ptr = tmp;
}
~Array()
{
free(_ptr);
}
//Array(const Array& obj) 先屏蔽掉
//{
// //……
//}
private:
int* _ptr;
};
int main()
{
Array arr1(5);
Array arr2(arr1); //语句一,你希望的行为是什么?
return 0;
}
不难看出,例2代码实现的是:利用类来包装一个数组(可能还有其它可延展功能)。对于上面代码的语句一
- 正常来说:我们应该是希望能够得到一个和
arr1
同等规模的独立数组arr2
。但是如果我们实现的是浅拷贝……
来看结果:
- 现象:这两个不同类对象的
_ptr
指针指向地址一模一样! - 如图理解:
这样就会造成两个问题:
- 当
arr1
访问这个数组的时候,会影响arr2
中的数据内容!这个读写的影响是相互的! - 注意到析构函数:
free(_ptr);
如果当arr1
和arr2
生命周期走到尽头,那么不管是谁先释放这片空间,都会造成:另一个对象对野指针的释放!!!这样是极度危险的行为!
所以我们需要对这样的类进行深拷贝 — 不仅仅是拷贝其成员变量的值,更需要拷贝其管理的申请的资源……
由于我们上面的那个示例2并没有能够得到当前数组的元素个数的接口,所以还是比较麻烦的,所以在下面的示例中,添加这个字段帮组我们获得数组的大小!
例3:
#include<iostream>
using namespace std;
class Array
{
public:
Array(int size)
:_size(size)
{
int* tmp = (int*)malloc(sizeof(int) * size);
if (tmp == nullptr)
{
perror("malloc");
return;
}
_ptr = tmp;
}
~Array()
{
free(_ptr);
_ptr = nullptr;
}
Array(const Array& obj)
:_size(obj._size)
{
_ptr = (int*)malloc(sizeof(int) * _size);
//拷贝
memcpy(_ptr, obj._ptr, sizeof(int) * _size);
//……
}
private:
int* _ptr = nullptr;
int _size;
};
int main()
{
Array arr1(5);
Array arr2(arr1);
return 0;
}
- 这样就完成了深拷贝的需求。当然不同场景下,深拷贝的方式是不同的,所以各位读者还是需要按需求来!
故此这里总结一下:
-
不需要进行深拷贝的类
- 不涉及动态资源申请的类
-
需要进行深拷贝的类
- 涉及动态资源申请的类
注: 如果有自定义类型不要担心,编译器默认生成的拷贝构造函数可以帮你调用其的拷贝构造函数。但是需要你的自定义类型成员实现了一个合理的拷贝方式!
完。
- 小编希望这篇文章能够帮助你!