刘禹锡《竹枝词二首·其一》
杨柳青青江水平,闻郎江上踏歌声。(一作唱歌)
东边日出西边雨,道是无晴却有晴。
如果属性有在堆区开辟内存的,一定要自己提供拷贝构造函数,进行深拷贝,以免堆区内存重复释放。
浅拷贝
- 浅拷贝会带来的问题是堆区空间重复释放
以下代码分析:
- Person类中,有两个成员变量,其中一个是指针类型。在有参构造中,此指针类型的成员变量指向的空间开辟在堆区。
- 在test1()中,Person p1(11, 160)会实例化对象p1, 会调用到有参构造,为p1.p_height开辟堆区空间,假设其指向的地址为0x1000,如上图。
- 在test1()中,Person p2(p1)会实例化对象p2,会调用到拷贝构造,Person类中没有自定义的构造函数,则会使用默认的构造函数,默认的拷贝构造进行简单的值拷贝,如上图,p2.p_height指向的堆区空间的地址0x1000,也就是说,p1.p_height和p2.p_height指向的是同一块内存。
- 在程序释放会先调用到p2的析构(栈区先进后出),delete p_height会释放p2.p_height指向的堆区空间0x1000,之后会调用到p1的析构,p1.p_height指向0x1000, 当delete p1.p_height时,0x1000的地址之前已经被p2的析构释放掉了,所以就会报错。
- 错误的原因是堆区只开辟了一个空间,但却遭遇了重复释放。
因为是浅拷贝,在调用第二个对象的析构函数时,会报错
code:
#include<iostream>
using namespace std;
#include"circle.h"
class Person
{
public:
int age;
int* p_height;
Person(int ref_age, int ref_height)
{
age = ref_age;
p_height = new int(ref_height);
cout << "堆区开辟的地址为: " << p_height << endl;
cout << "有参构造函数被调用," << "年龄是:" << age << ",身高是:" << *p_height << endl;
}
~Person()
{
cout << p_height << endl;
if(p_height != NULL)
{
delete p_height;
p_height = NULL;
}
cout << "析构函数被调用" << endl;
}
};
void test1()
{
Person p1(11, 160);
Person p2(p1);
}
void main()
{
test1();
system("pause");
}
result:
堆区开辟的地址为: 0000020FB4F36310
有参构造函数被调用,年龄是:11,身高是:160
0000020FB4F36310
析构函数被调用
0000020FB4F36310
报错
为了解决浅拷贝带来的堆区内存重复释放的问题,就可以已使用深拷贝。
深拷贝
- 自己写拷贝构造函数。
- 在拷贝构造中重新在堆区申请内存空间
以下代码分析: - Person类中,有两个成员变量,其中一个是指针类型。
- 在有参构造中,此指针类型的成员变量指向的空间开辟在堆区。
- 在test1()中,Person p1(11, 160)会实例化对象p1, 会调用到有参构造,为p1.p_height开辟堆区空间,假设其地址为0x1000,如上图。
- 在test1()中,Person p2(p1)会实例化对象p2,会调用到拷贝构造。
- 在拷贝构造中,p_height = new int(ref_p.p_height) 在堆区开辟内存,假设其内存地址为0x2000,该内存中的内容为ref_p.p_height,(ref_p.p_height是个指针,*ref_p.p_height是160,其为p1.p_height指向的内容),如上图。也就是说,p1.p_height和p2.p_height指向的是不同的内存,但该内存中存放的内容是一样的。
- 在程序释放会先调用到p2的析构(栈区先进后出),delete p_height会释放p2.p_height指向的堆区空间0x2000,之后会调用到p1的析构,p1.p_height指向0x1000,不会有重复释放的问题。
code:
#include<iostream>
using namespace std;
#include"circle.h"
class Person
{
public:
int age;
int* p_height;
Person(int ref_age, int ref_height)
{
age = ref_age;
p_height = new int(ref_height);
cout << "堆区开辟的地址为: " << p_height << endl;
cout << "有参构造函数被调用," << "年龄是:" << age << ",身高是:" << *p_height << endl;
}
Person(const Person& ref_p)
{
age = ref_p.age;
p_height = new int(*ref_p.p_height); // 堆区重新申请空间
//p_height = ref_p.p_height; //编译器默认实现的是这行代码
cout << "拷贝构造函数被调用, " << "堆区开辟的地址为:" << p_height << endl;
}
~Person()
{
cout << p_height << endl;
if(p_height != NULL)
{
delete p_height;
p_height = NULL;
}
cout << "析构函数被调用" << endl;
}
};
void test1()
{
Person p1(11, 160);
Person p2(p1);
}
void main()
{
test1();
system("pause");
}
result:
堆区开辟的地址为: 0000021DED9361D0
有参构造函数被调用,年龄是:11,身高是:160
拷贝构造函数被调用, 堆区开辟的地址为:0000021DED936990
0000021DED936990
析构函数被调用
0000021DED9361D0
析构函数被调用