首页 > 系统相关 >C++深拷贝构造函数解决浅拷贝的堆区内存重复释放问题

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

时间:2024-07-25 12:25:05浏览次数:11  
标签:p2 堆区 Person 地址 拷贝 构造函数

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.总结

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

标签:p2,堆区,Person,地址,拷贝,构造函数
From: https://blog.csdn.net/weixin_47768406/article/details/140685100

相关文章

  • modint 默认构造函数的一些想法
    今、在zhengruioi.com上参加模拟赛时被卡常了。这道题目涉及对\(998244353\)取模的操作,故我使用我自制的由atcoder::static_modint改写而来的modint完成了代码,这两个板子大致如下://modinttemplate<unsignedumod>structmodint{/*{{{*/staticconstexprintmod......
  • 8. 深浅拷贝、可变与不可变、垃圾回收机制、字符编码、文件操作
    1.可变和不可变数据类型1.1概念可变数据类型:当指定值被修改时内存空间地址不变不可变数据类型:当指定值被修改时内存空间地址发生改变1.2常见类型的代码实现可变类型:list  dict set不可变类型:strint floatbooltuple(1)整数  不可变a=1print(id(a))#25......
  • TopoDS_Shape的拷贝
    TopoDS_Shape的拷贝有两种方式1)TopoDS_ShapenewShape=oldShape;2)BRepBuilderAPI_Copytool;tool.perform(oldShape,true,false);//!"false"sinceI'mnotinterestedincopyingthetriangulationnewShape=tool.Shape();两者的不同在于shape数据的拷贝深度,TopoDS......
  • 意外的函数行为关键参数列表构造函数
    我已经将这个简单的代码简化为更大的python代码中的意外行为:deff(n,s=[]):s.append(n)returnsprint(f(3))print(f(5))给出的输出是:[3][3,5]我期望:[3][5]如果明确给出空列表作为参数,则f(5,s=[]),它按预期工作。我认为这是一个不好的做法,......
  • 【c++经典面试题】有关string类的深浅拷贝
    题目背景基于自实现string类substr成员函数时遇到的问题。代码展示stringstring::substr(size_tpos,size_tlen)//声明时len的参省值位npos { assert(pos<_size); if(len>_size-pos)//如果len的长度大于有效字符长度,那么重置为有效字符长度 { le......
  • 数据共享(浅拷贝)与数据独立(深拷贝)
    在FFmpeg中,数据共享和数据独立的区别在于浅拷贝和深拷贝的使用。让我们详细探讨这两个概念及其在FFmpeg内存模型中的实现。数据共享(浅拷贝)浅拷贝是指在拷贝对象时,只拷贝对象的引用,而不拷贝实际的数据内容。对于FFmpeg中的AVPacket来说,浅拷贝意味着两个Packet共享同一个数据缓冲......
  • 浅析JS构造函数
    构造函数(ConstructorFunction)是JavaScript中创建对象的一种重要方式,它不仅让我们能够创建具有相似属性和方法的对象,还能充分利用JavaScript的原型继承机制,实现代码的高效复用。本文将深入探讨构造函数的原理、使用方法、与类的关系,以及一些高级用法和注意事项。构造函数的基......
  • 34.拷贝数组
    定义一个方法:copyOfRange(int[]arr,intx,inty)将数组arr中从索引x开始(包含x)到索引y结束(不包含y)中的元素,复制到新数组中,并将新数组返回例:原始数组arr={1,2,3,4,5,6,7,8,9},新数组newArr={4,5,6,7}publicstaticvoidmain(String[]args){//1.静态初始化定......
  • C/C++ 《二级指针浅拷贝》
    背景A对象内部属性a属于int,动态分配内存回收,析构函数deleteA**aptr=newA[10]申请10个空间长度的A*类型测试浅拷贝测试代码#include<iostream>usingnamespacestd;classA{public:int*a;A(inti){//构造函数a=newint(i);}~A(......
  • 从零开始学Java(超详细韩顺平老师笔记梳理)05——数组(语法,赋值机制,拷贝反转)、排序(冒泡排
    文章目录前言一、数组1.基础语法1)介绍2)使用(动态、静态初始化语法与使用)3)注意事项和细节2.数组赋值机制(ArryAssign)3.数组拷贝4.数组反转(reserve)5.数组的扩容与缩减二、排序三、查找四、二维数组(TwoDimensionalArry)1.快速入门2.使用3.案例:打印一个10行的......