C++编程语言中类对象的赋值与复制介绍(三)

本系列文章主要介绍C++编程语言中类对象的赋值操作和复制操作,以及两者之间的区别,另外还会介绍“深拷贝”与“浅拷贝”的相关知识。

本文为系列文章的第三篇,主要介绍C++编程语言中的“深拷贝”和“浅拷贝”,以及赋值运算符的重载、拷贝构造函数的重载的相关知识。

1 浅拷贝

1.1 What

浅拷贝:当进行对象拷贝时,只拷贝类中位于stack域中的内容,而不会拷贝heap域中的内容。

例如,使用类的默认的赋值运算符“=”,或默认的拷贝构造函数时,进行的对象拷贝都属于浅拷贝。这也说明,“浅拷贝”与使用哪种方式(赋值运算符或是拷贝构造函数)进行对象拷贝无关。

1.2 问题

浅拷贝会有一个问题,当类中存在指针成员变量时,进行浅拷贝后,目标对象与源对象的该指针成员变量将会指向同一块heap内存区域(而非每个对象单独占据一块内存区域),这就可能导致由于共用该段内存区域而产生内存覆盖、重复释放内存等问题。详情可参考本系列第一篇文章的相关内容。

所以,针对带有指针的类对象的拷贝操作,正确的做法是:使两个对象的指针各自指向不同的内存区域,即在拷贝时不是简单地拷贝指针,而是将指针指向的内存中的每一个元素都进行拷贝,由此也就引出了“深拷贝”的概念。

2 深拷贝

深拷贝:当进行对象拷贝时,将对象位于stack域和heap域中的数据都进行拷贝。

前面也提到了,类默认提供的赋值运算符或拷贝构造函数,进行的都是浅拷贝,所以,为了实现对象的深拷贝,需要对赋值运算符或拷贝构造函数进行重载,以达到深拷贝的目的。

2.1 赋值运算符的重载

此处展示一段重载赋值运算符的示例代码,内容如下:

    // 重载赋值运算符
    ClassA& operaton=(const ClassA& obj)
    {
        // 适应自赋值(obj = obj)操作
        if (this == &obj)
        {
            return *this;
        }

        // 新建 heap 空间
        int iLength = strlen(str.m_pData);
        char* pTemp = new char[iLength + 1];

        // 新建 heap 空间成功后,再释放掉已有的 heap 空间
        // 保证异常安全性
        if (m_pszName != NULL)
        {
            delete []m_pszName;
            m_pszName = NULL;
        }

        m_pszName = pTemp;
        // 拷贝 heap 空间的内容
        strcpy(m_pszName, obj.m_pszName);

        // 拷贝 stack 域的值
        m_nId = obj.m_nId;
        
        return *this;
    }
    
private:
    int m_nId;
    char* m_pszName;

针对上面的赋值运算符重载函数,说明如下:

  1. 需要将函数返回值的类型声明为类的引用,同时函数返回实例自身的引用(*this)——只有返回一个引用,才可以允许连续赋值,如“a = b = c”;
  2. 需要将入参的类型声明为常量引用(const 类名& 实例名),避免函数入参的拷贝构造函数调用,提高代码效率;
  3. 需要考虑自赋值情况;
  4. 需要释放实例自身已有的内存,避免内存泄漏;
  5. 需要考虑内存申请异常,避免申请内存(new char)失败后,实例中的指针变为空指针,因此需要先执行申请内存操作,内存申请成功后,再释放实例的heap空间。

2.2 拷贝构造函数的重载

这里展示一段重载拷贝构造函数的示例代码,内容如下:

    // 重载拷贝构造函数,重载后的拷贝构造函数支持深拷贝
    ClassA(ClassA &obj)
    {
        // 拷贝 stack 域的值
        m_nId = obj.m_nId;
        // 新建 heap 空间
        m_pszName = new char[strlen(obj.m_pszName) + 1];
        // 拷贝 heap 空间的内容
        if (m_pszName != NULL)
        {
            strcpy(m_pszName, obj.m_pszName);
        }
    }
    
private:
    int m_nId;
    char* m_pszName;

2.3 总结

从上述两个示例代码可以看出,支持深拷贝的重载赋值运算符和重载拷贝构造函数相似,但两者也存在以下区别:

  • 重载赋值运算符的返回值需要是对象的引用,以进行链式赋值(obj3 = obj2 = obj1);而重载拷贝构造函数因为属于构造函数的一种,所以不需要返回值;
  • 重载赋值运算符要释放掉对象自身的heap空间(如果存在的话),以避免内存泄漏;而重载拷贝构造函数无需如此,因为拷贝构造函数函数是在创建(并初始化)对象时调用的,对象此时还没有分配heap空间;
  • 如果在重载赋值运算符和重载拷贝构造函数都可以解决问题时,建议选择重载拷贝构造函数,因为后者貌似坑少一些:)。

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liitdar

赠人玫瑰,手有余香,君与吾共勉

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值