前言
已从工作三年的公司离职,今天我久违的徒弟惨叫着发来一个bug……看到之后,我有些沉默。我怀疑不帮他,微信铃声可能会一直响个不停。
这个问题有些基础,但也是c++新手容易犯的错误之一
问题
由于代码保密,这里简单的抽象出一个demo,简单说明下问题
class Phone
{
public:
Phone()
{
mBrand = new char(20);
}
~Phone()
{
if (mBrand != NULL)
{
// p2执行时因为多次释放崩溃
delete mBrand;
mBrand = NULL;
}
}
private:
//品牌
char *mBrand;
}
int main()
{
Phone p1;
//这里使用了编译器提供的默认的拷贝构造
Phone p2(p1);
return 0;
}
徒弟在使用了编译器提供的默认拷贝构造函数,在这些完方法,要对Phone对象进行析构时,对mBrand进行了多次释放导致崩溃
解析
为什么会出现这个问题?
浅拷贝
当我们未定义拷贝构造函数,系统则会调用默认拷贝构造函数。这个拷贝函数做的事就是使用同一类中之前创建的对象来初始化新创建的对象。
于是这里的这段代码我们可以这样补充出来
Phone(const Phone &p) //系统创建出的一个拷贝构造
{
mBrand = p.mBrand;
}
我们根据代码和图示,说明一下步骤:
1. Phone p1触发了无参构造 ,为mBrand在堆上开辟了地址未0x0012的内存空间,这个地址上的值是char(20);
2. Phone p2(p1)触发了系统的默认拷贝构造函数,根据上面补充出来的代码,我们知道,p2的mBrand指针也是指向了0x0012;
3. 当main函数执行完,p2开始析构,因为mBrand=0x0012,不为NULL,所以对0x0012上的数据即char(20)进行了释放操作;
4. 接着p1开始析构,因为他自己的成员变量mBrand=0x0012,不为NULL,所以再对0x0012上的数据即char(20)进行了释放操作,但是因为步骤3中已经释放过一次了,所以会触发多次释放的崩溃错误;
解决方法
深拷贝
针对以上情况,我们可以重新在堆申请一块内存0x0022,让这块儿内存同样保存值char(20),这样不同的对象p1,p2的mBrand都是指向不同的地址0x0012,0x0022。就可以在保证储存值相同的情况下,也不怕导致同一块内存被重复释放。
Phone(const Phone &p)
{
//深拷贝
mBrand = new char(20);
memcpy(mBrand, p.mBrand, strlen(p.mBrand));
}
注:
以上代码仅为说明问题,临时缩减的demo 代码,可能并不合理严谨,仅供分析问题使用。
总结
如果类中属性有在堆中申请内存空间的,务必自己提供拷贝构造,处理好浅拷贝所带来的问题。