P1 C++学习:浅拷贝问题与赋值运算符重载

文章讨论了在C++中,特别是类对象中包含堆内存指针时,浅拷贝构造函数可能导致的问题,如内存泄漏和悬挂指针。作者强调了使用深拷贝构造函数和重载赋值运算符的重要性,以确保正确管理内存和避免潜在错误。
摘要由CSDN通过智能技术生成

#浅拷贝问题的出现
我们常常使用类的拷贝构造函数来初始化类对象。而类对象在创建和销毁时会分别调用构造函数和析构函数。
如下代码:

#include "iostream"
#include "vector"
using namespace std;
class person{//creat a class named person
public:
    int id;
    int *m_Age;

    person(int _id,int _m_Age){
        id = _id;
        m_Age = new int(_m_Age);//use the heap to allocate memory
    }
    ~person(){
        if(m_Age!=NULL){
            delete m_Age;
            m_Age = NULL;
        }
    }
};

void test1 (){
    person p1(5, 15);
    person p2 = p1;//call the copy constructor
}
int main(){
    test1();
    return 0;
}

当类成员中有指针变量需要在堆区开辟内存,且我们要调用编译器(MSCV)自动创建的拷贝构造函数时,逐步调试,出现浅拷贝问题,即。

在这个浅拷贝过程中,p2 的 id 和 m_Age 会被新的值所覆盖。对于 id 来说,这并不是问题,因为它是一个基本类型。但是对于 m_Age 来说,这就是问题了。m_Age 是一个指向 int 的指针,它指向的内存是在构造函数中通过 new 分配的。拷贝时系统默认将p1的内容复制给p2,因此就会使p1与p2的m_Age指针成员所指向同一块堆区内存。在p2发生析构之后p2.m_Age所指向的内存空间被释放并置空,此时p1.m_Age仍指向已被释放的之前的地址空间。因此,p1析构时,发现p1指针不空,就会再次使用delete释放非法的地址空间。
要解决上述问题,可以手动添加拷贝构造函数

person::person(const person& p) {
        id = p.id;
        m_Age = new int(*(p.m_Age));
    }

应当注意的是,在使用拷贝构造函数时,传入的相同类型的类应加上const和&:
在这里插入图片描述
#赋值运算符的重载
继续,若执行如下代码:

#include "iostream"
#include "vector"
using namespace std;
class person
{
public:
    person(int _id, int _m_Age) {
        cout << "地址为" << this << "的构造函数调用" << endl;
        id = _id;
        m_Age = new int(_m_Age);
    }
    person(const person& p);
    
    int id;
    int* m_Age;
    ~person() {
        cout << "地址为" << this << "的析构函数调用" << endl;
        if (m_Age != NULL) {
            delete m_Age;
            m_Age = NULL;
        }
    }
};

person::person(const person& p) {
    cout << "地址为" << this << "的构造函数调用" << endl;
    id = p.id;
    m_Age = new int(*(p.m_Age));
}

void test2() {
    person p1(5, 15), p2(3, 13);
    cout << "p1地址" << &p1 << endl;
    cout << "p2地址" << &p2 << endl;
    p2 = person(p1);//or use p2 = p1;
}
int main() {
    test2();
    return 0;
}

逐步调试,发现仍会出现浅拷贝的问题。p2 = person(p1); 这一句其实涉及到了两个操作:

  1. person(p1):这是一个拷贝构造函数的调用,会创建一个新的 person 对象,该对象的 id 和 m_Age 的值是从 p1 中拷贝过来的。这个新对象在堆栈上创建,是一个临时对象

  2. p2 =:这是一个赋值操作,它会把等号右边的对象(即刚刚创建的临时对象)赋值给 p2。但是,person 类没有定义赋值运算符的重载,所以这里会使用默认的赋值运算符,它会进行浅拷贝。这就是问题的根源。

在赋值操作中,p2.m_Age 的旧值(也就是它原来指向的内存地址)会被新的值(也就是临时对象的 m_Age 所指向的内存地址)所覆盖,而 p2.m_Age 原来指向的内存并没有被 delete,这就造成了内存泄漏

同时,因为 p2.m_Age 和临时对象的 m_Age 现在指向同一块内存,当临时对象在语句结束后被析构时,这块内存会被 delete,而** p2.m_Age 还在指向这块已经被释放的内存**,这就造成了 p2.m_Age 是一个悬挂指针,这是非常危险的。

解决这个问题的方法就是为 person 类定义一个赋值运算符的重载,进行深拷贝:

person& person::operator=(const person& p) {
if (this != &p) {
delete m_Age;
id = p.id;
m_Age = new int(*(p.m_Age));
}
return *this;
}
这样,在赋值操作中,p2.m_Age 原来指向的内存就会被正确地释放,而且 p2.m_Age 会指向一块新的、和临时对象的 m_Age 所指向的内存不同的内存。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值