容器元素的深拷贝和浅拷贝问题

时间:2022-02-25 19:46:44

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);
    }

另:最好再重载一个 = 运算符
参考资料
《程序员面试宝典》(第四版)