Bootstrap

C++深拷贝构造函数解决浅拷贝的堆区内存重复释放问题

1.简单介绍

先简单介绍一下浅拷贝和深拷贝:

浅拷贝->简单的赋值拷贝操作,默认的拷贝构造函数就是浅拷贝。
深拷贝->在堆区重新申请空间,进行拷贝操作。

2.问题展示

下面用代码示例明了地展示默认拷贝构造函数浅拷贝带来地堆区内存重复释放问题:

#include<iostream>
using namespace std;

class Person
{
public:
	int m_Age;
	int* m_Height;

public:
	Person()
	{
		cout << "Person的默认构造函数调用" << endl;
	}

	Person(int a, int h)
	{
		cout << "Person有参构造函数调用" << endl;
		m_Age = a;
		m_Height = new int(h);//new返回int型堆区指针
	}

	//浅拷贝带来的问题就是堆区的内存重复释放
	~Person()
	{
		//析构代码,将堆区开辟数据做释放操作
		//注意这里p2的m_Height置空以后,p1的m_Height并没有改变,不是空,就是原来p1的m_Height地址
		cout << "m_Height = " << m_Height << endl;
		if (m_Height != NULL)
		{
			delete m_Height;
			m_Height = NULL;
		}

		cout << "析构后的m_Height = " << m_Height << endl;
		cout << "Person析构函数调用" << endl;
	}
};

//不写深拷贝构造函数会报错,因为m_Height堆区内存重复释放了2次,先释放p2,p1的m_Height内存地址相同,无法再次释放了
void test01()
{
	Person p1(18,166);

	cout << "p1的年龄为:" << p1.m_Age << " 身高为:" << *p1.m_Height << endl;
	cout << "p1的地址为:" << (int*)&p1 << endl;

	Person p2(p1);

	cout << "p2的年龄为:" << p2.m_Age << " 身高为:" << *p2.m_Height << endl;
	cout << "p2的地址为:" << (int*)&p2 << endl;
}

int main() {
	test01();
}

这段代码运行会报错,原因就是test01()执行完后,由于先进后出,对象p2的m_height地址0000027F7D1407E0根据~Person()析构函数delete自动释放了,输出中也可见m_Height地址值变成了为0000~0000,也就是NULL。这时候p1自动调用析构函数要释放地址,要再次delete地址0000027F7D1407E0,但是这个地址p2析构已经delete了,所以会报错。

图例解释浅拷贝带来的堆区内存重复释放问题:

3.解决办法

解决办法:利用深拷贝构造函数,p2拷贝p1时,地址值不再是简单复制,而是给p2属性创建一个新的地址值,和p1属性的地址值不同,这样p1属性地址delete后对p2属性地址没有影响。可以理解为p2属性的地址独立出来了。

深拷贝解决问题后的代码示例如下:

#include<iostream>
using namespace std;

class Person
{
public:
	int m_Age;
	int* m_Height;

public:
	Person()
	{
		cout << "Person的默认构造函数调用" << endl;
	}

	Person(int a, int h)
	{
		cout << "Person有参构造函数调用" << endl;
		m_Age = a;
		m_Height = new int(h);//new返回int型堆区指针
	}

	//深拷贝解决浅拷贝重复释放问题
	Person(const Person& p)
	{
		cout << "Person拷贝构造函数调用" << endl;
		m_Age = p.m_Age;
		//m_Height = p.m_Height;编译器默认实现的就是这行代码,即浅拷贝操作
		// 
		//深拷贝操作,重新创建一个地址
		m_Height = new int(*p.m_Height);//要加*,不然括号内只是地址,不是身高值
	}

	//浅拷贝带来的问题就是堆区的内存重复释放
	~Person()
	{
		//析构代码,将堆区开辟数据做释放操作
		//注意这里p2的m_Height置空以后,p1的m_Height并没有改变,不是空,就是原来p1的m_Height地址
		cout << "m_Height = " << m_Height << endl;
		if (m_Height != NULL)
		{
			delete m_Height;
			m_Height = NULL;
		}

		cout << "析构后的m_Height = " << m_Height << endl;
		cout << "Person析构函数调用" << endl;
	}
};

//不写深拷贝构造函数会报错,因为m_Height堆区内存重复释放了2次,先释放p2,p1的m_Height内存地址相同,无法再次释放了
void test01()
{
	Person p1(18,166);

	cout << "p1的年龄为:" << p1.m_Age << " 身高为:" << *p1.m_Height << endl;
	cout << "p1的地址为:" << (int*)&p1 << endl;

	Person p2(p1);

	cout << "p2的年龄为:" << p2.m_Age << " 身高为:" << *p2.m_Height << endl;
	cout << "p2的地址为:" << (int*)&p2 << endl;
}

int main() {
	test01();
}

//总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数防止浅拷贝带来的问题

可以看到运行结果不再报错:

4.总结

如果属性有在堆区开辟的,一定要自己提供深拷贝构造函数防止浅拷贝带来的问题

;