浅拷贝:两个对象所指向的地址是同一个地址
深拷贝:两个对象的值相同,并且他们都有独立的地址
对于C++而言,如果不提供自己写的拷贝构造函数,编译器会生成默认的
1.将目标对象的值逐个拷贝过来
2.如果传递的是指针的话,所拷贝的是指针的值,也就是指向的地址,而不是指向的对象(浅拷贝)
3.在析构函数中释放内存时,其他对象中的指针可能还在指向已经被释放的地址,如果再次调用析构函数,则会delete被释放的资源,导致报错
示例:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Person
{
private:
string name{"name"};
double *height {nullptr};
public:
string get_name() {return name;};
double get_height(){return *height};
//构造函数
Person(string name = "none",double height = 0.0);
//析构函数
~Person();
}
//构造函数
Person::Person(string name,double height)
{
this->name = name;
this->height = new double {height};
}
//析构函数
Person::~Person(){
if (height!=nullptr)
delete height;
}
int main()
{
Person Mike {'Mike',175.0};
Person new_person {Mike};
return 0;
}
在上述例子中,在Person类中定义的指向Person的height属性的指针,并没有定义自身的拷贝构造函数,因此编译器会默认产生浅拷贝的拷贝构造函数,与以下函数等同:
// 默认构造函数
Person(const Person &source)
{
this->name = source.name
this->height = source.height
}
默认拷贝构造函数是以值传递的形式进行的,因此它会把Mike的name属性赋值给new_person的name,把指向Mike的height属性的地址也会传给new_person的地址,因此new_person和Mike两个对象的height属性的地址指向的是同一个地址。而在程序结束是将会执行析构函数释放每个变量的内存,因为在栈上面定义,因此遵循后人先出的规则,先释放new_person的内存,将占有heigt属性的地址释放后继续释放Mike的height属性的地址,因为这两个指针的地址一样,因此,第二次释放时,会导致报错。
解决这个问题的方式可以通过自定义拷贝构造函数:
//声明拷贝构造函数
Person(const Person &source);
//定义拷贝构造函数
Person::Person(const Person &source)
{
this->name = source.name;
this->height = new double {*(source.height)};
}
如此就会创建一个新的变量,并将传入的变量的指针解引用后的值传递给函数中,并在堆中申请一块新的地址指向新的变量,因此两个变量都拥有独立的值和地址,这样执行析构函数就不会报错了。