STL容器所提供的都是值(value)寓意,而非引用(reference)寓意,也就是说当我们给容器中插入元素的时候容器内部实施了拷贝动作,将我们要插入的元素再另行拷贝一份放入到容器中,而不是将原数据元素直接放进容器中,也就说我们提供的元素必须能够被拷贝 .
看下面的代码:
//容器元素的深拷贝和浅拷贝问题
#include "pch.h"
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;
class CDemo
{
public:
CDemo() :str(NULL) {}
~CDemo()
{
if (str)
{
delete[] str;
}
}
char* str; //指针,容易带来浅拷贝的问题
};
int main()
{
CDemo d1;
d1.str = new char[32];
strcpy_s(d1.str,strlen("trend micro") + 1,"trend micro");
vector<CDemo>* a1 = new vector<CDemo>;
a1->push_back(d1); //只是简单的值拷贝
delete a1; //同一块内存析构了两次
return 0;
}
这个程序在退出的时候会出现问题,重复delete同一片内存,程序崩溃。
将析构函数修改如下,可以更加清楚的看到问题所在:
~CDemo()
{
if (str)
{
static int i = 0;
cout << "&CDemo" << i++ << "=" << (int*)this << ",str = " << (int *)str << endl;
delete[] str;
}
}
也就是说,发生了CDemo类的两次析构,并且两次析构str所指向的同一内存地址空间(两次str的值相同)。
问题出在哪里?
有人认为vector对象指针能够自动析构,所以不需要调用delete a1,否则会造成两次析构对象。这种理解是不准确的,任何对象如果是通过new操作符申请了空间,必须显示的调用delete来销毁这个对象,所以delete a1这条语句是没有错误的。
错误在于:
在执行
a1->push_back(d1); //只是简单的值拷贝
这条语句时,会调用CDemo的拷贝构造函数,虽然CDemo类中没有定义拷贝构造函数,但是编译器会为CDemo类构建一个默认的拷贝构造函数(浅拷贝),正是这里出了问题,a1中的所有CDemo元素的str成员变量没有初始化,只有一个四字节(32位机)指针空间。
a1->push_back(d1); //只是简单的值拷贝
这句话执行完之后,a1里的CDemo元素与d1是不同的对象,但是a1里的CDemo元素的str与d1.str指向的是同一块内存。
局部变量“CDemo d1;”在main函数退出时,自动释放所占内存空间,前面已经调用过delete a1,已经把d1.str释放了,main函数退出时,又要释放掉已经释放掉的d1.str内存空间,所以程序最后崩溃。
解决办法:
给CDemo类添加一个拷贝构造函数即可
CDemo(const CDemo &cd)
{
this->str = new char[strlen(cd.str) + 1];
strcpy_s(str,strlen(cd.str) + 1,cd.str);
}
另:最好再重载一个 = 运算符
参考资料
《程序员面试宝典》(第四版)