在C++中,编译器为某一个类提供了3个默认函数,其中包括默认的构造函数,默认的赋值构造函数(又叫拷贝构造函数),默认的析构函数。
默认构造函数----编译器提供的无参的默认函数
1. 如果程序员自己不提供默认的函数,则编译器会自己创建一个默认构造函数;
2. 如果程序员自己定义一个有参的构造,则编译器不会在提供默认的无参构造函数。
默认析构函数----编译提供的默认析构函数是空实现,没有任何的代码,如果想要释放堆区的数据,需要程序员自己定义析构函数的具体实现,析构函数和构造函数一样没有返回值。
默认拷贝构造函数----特殊的一种构造函数,它唯一的一个参数是自身类的一个引用变量,且是const类型
1. 编译提供的默认拷贝函数是简单的复制过程,即把一个对象的属性值复制到另一个对象的属性中去,相当于等号'='的赋值运算。
2. 如果程序员自己定义拷贝构造函数,则编译器不会提供其它构造函数(包括默认的构造函数)
默认的拷贝构造函数是浅拷贝,正如上述是将一个对象的所有属性值复制到另一个对象当中,如果有下面一个类
Class Person
{
private:
string m_Name;
int* m_PtrAge;
};
这个类还有2个私有属性,一个姓名,一个年龄,只不过这里的年龄需要在堆区创建,这里提供一个含有2个参数的构造函数,用于创建一个对象
Person::Person(string name, int age)
{
this->m_Name = name;
this->m_PtrAge = new int(age);
}
所以在析构函数中需要释放在堆区的空间,具体如下:
Person::~Person()
{
cout << "Persion destructor invoking." << endl;
if (this->m_PtrAge != NULL)
{
delete this->m_PtrAge;
this->m_PtrAge == NULL;
}
}
如果先创建一个对象“刘备”,再利用编译提供的默认构造函数创建一个刘备的副本
Person P1("刘备", 46);
Person P2(P1);
在对象生命周期结束后,直接抛以下的异常
造成以上crash的主要原因是因为编译器提供的默认拷贝构造函数只是做了简单的赋值操作,即浅拷贝,导致堆区的内存重复释放
编译器提供的默认拷贝构造函数如下:
Person::Person(const Person& myPerson)
{
this->m_Name = myPerson.m_Name;
this->m_PtrAge = myPerson.m_PtrAge;
}
P1的生命周期结束后,判断m_PtrAge是否为空,很明显m_PtrAge的值是0x0100,不为空,释放m_PtrAge指向的内存;
P2的生命周期结束后,同样判断m_PtrAge是否为空,很明显m_PtrAge的值也是0x0100,也不为空,再次释放m_PtrAge指向的内存空间;
两次释放都指向同一块内存空间,造成堆区的数据重复释放,固有以上的crash信息,这就是浅拷贝带来的风险
解决办法:
利用深拷贝解决浅拷贝带来的风险,即让P2对象中的m_PtrAge指向另一个内存单元,这样2个对象调用析构函数后,各自释放自己的内存单元
注:上述2个图列中的地址单元0x0100和0x0200均为举例,不具有任何实际的意义
按照以上的思路重新修改拷贝构造函数,让它变成深拷贝构造函数:
//overload Constructor
Person::Person(const Person& myPerson)
{
cout << "Person Constructor invoking." << endl;
this->m_Name = myPerson.m_Name;
this->m_PtrAge = new int(*(myPerson.m_PtrAge));
}
同时,重载等号'='操作符,这里稍微要多加一步,如果之前的m_PtrAge已经有内存地址,需要在赋值前先清空:
//overload operator=
Person& Person::operator=(const Person& myperson)
{
cout << "Person Operator= invoking." << endl;
if (this->m_PtrAge != NULL)
{
delete this->m_PtrAge;
this->m_PtrAge = NULL;
}
this->m_Name = myperson.m_Name;
this->m_PtrAge = new int(*(myperson.m_PtrAge));
return *this;
}
总结:
1. 浅拷贝带来的隐患是堆区的内存重复释放;
2. 利用深拷贝和重载等号'='运算符,可以解决堆区数据重复释放的问题;