C++:浅拷贝和深拷贝

深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是在编程中经常遇到的两种复制数据的方式,它们在操作上有着明显的区别。

浅拷贝

复制代码
浅拷贝是一种拷贝对象的方式,其中对象的成员变量的值被简单地复制到新对象中,而不涉及对成员变量所指向的内存的复制。在浅拷贝中,新对象和原对象共享相同的动态分配的内存地址,这意味着它们指向相同的内存块,而不是各自拥有独立的内存块。

浅拷贝通常是通过默认的拷贝构造函数或赋值运算符来实现的。当对象中没有动态分配的内存,或者对象的成员变量是简单的基本数据类型时,浅拷贝是安全且有效的。但是,在涉及到动态分配的内存(如指针)时,浅拷贝可能会导致潜在的问题。

示例
cpp 复制代码
class RolePlayer {
public:
    //拷贝构造函数
    RolePlayer(const RolePlayer & other) : m_NPCName(other.m_NPCName), m_nHP(other.m_nHP)
    {
        std::cout << "拷贝构造函数执行成功" << std::endl;
    }
    //析构函数
    ~RolePlayer()
    {
        if (m_NPCName != nullptr) {
        delete m_NPCName;
        }
        std::cout << "析构函数" << std::endl;
    }
    
    //设置对象名
    void SetName(char * m_NPCName) {
        this->m_NPCName = m_NPCName;
    }
    
    //获取对象名
    char * GetName() {
        return this->m_NPCName;
    }
    
private:
    char * m_NPCName = nullptr;
    int m_nHP;
}

RolePlayer 类中,拷贝构造函数执行了浅拷贝,而析构函数释放了 m_NPCName 指针指向的内存。这个实现中存在潜在的问题,因为在拷贝构造函数中只是简单地复制了指针的值,而不是复制指针所指向的内存块。

现在将在主函数中对类进行调用:
cpp 复制代码
int main() {
    //创建对象roleA
    RolePlayer roleA;
    char szBuffer[] = "WolF1";
    roleA.SetName(szBuffer);
​
    //创建roleB且调用拷贝构造函数
    RolePlayer roleB(roleA);
    std::cout << roleB.GetName() << std::endl;
    
    system("pause");
    return 0;
}

首先创建了一个 RolePlayer 对象 roleA,然后使用 SetName 方法将字符串 "WolF1" 设置为 roleA 的名称。接着,创建了另一个 RolePlayer 对象 roleB,并通过拷贝构造函数将 roleA 的值复制到 roleB 中,并打印出roleB对象的名称,程序执行的结果为:

截止目前位置程序的执行一切正常,但是当我们跟着程序的提示按下任意键结束程序时发生了错误;

那么为什么会出现问题呢?原因就出现在拷贝构造函数中;该拷贝构造函数指定了初始化列表,相当于在构造之前就直接进行对象成员变量的初始化,那么我们直接将目光锁定在m_NPCName成员变量中;

cpp 复制代码
RolePlayer(const RolePlayer & other) : m_NPCName(other.m_NPCName), m_nHP(other.m_nHP)

按照这种方式直接进行成员变量的初始化等于直接将roleBm_NPCName成员地址指向roleAm_NPCName地址;那么这个时候再执行析构函数:

cpp 复制代码
    //析构函数
    ~RolePlayer()
    {
        if (m_NPCName != nullptr) {
        delete m_NPCName;
        }
        std::cout << "析构函数" << std::endl;
    }

这个时候在roleA对象的析构函数执行的时候他的m_NPCName成员就会被释放,那么这个时候roleB对象的析构函数执行的时候在执行delete m_NPCName;时就出现双重释放漏洞从而产生报错。除此之外还有SetName函数也是浅拷贝,也会出现相同的结果。

双重释放漏洞
复制代码
双重释放漏洞是指在程序中释放已经被释放的内存块的情况。这种情况下,同一块内存可能被释放两次或多次,导致程序出现未定义的行为、内存损坏或崩溃。

为了解决这个问题,就应该使用深拷贝来确保每个对象都有自己独立的内存块。这意味着在拷贝构造函数中应该动态分配新的内存,并将原始字符串的内容复制到新的内存空间中。同时,在析构函数中应该释放动态分配的内存。(即拷贝构造函数和SetName函数都需要执行深拷贝)。

深拷贝

深拷贝是一种拷贝对象的方式,其中对象的成员变量及其所指向的内存都被复制到新对象中。这意味着新对象拥有与原对象相同的值,但是它们的内存空间是相互独立的,即使一个对象被修改也不会影响到另一个对象。

深拷贝通常发生在以下情况下:

  1. 对象中包含动态分配的内存:如果对象中的成员变量是指针,并且这些指针指向动态分配的内存(例如使用 new 运算符动态分配的内存),则通常需要深拷贝来确保新对象拥有自己独立的内存空间。

  2. 对象中包含其他对象:如果对象中包含其他对象作为成员变量,并且这些对象也需要进行复制,那么通常需要深拷贝来递归地复制所有相关对象。

  3. 对象需要完全独立的副本:在某些情况下,即使对象中的成员变量是基本数据类型,但也需要创建一个完全独立的副本,以避免对原始对象的修改影响到新对象。

修改后的代码:
SetName
cpp 复制代码
void SetName(char * m_NPCName) {
    //执行深拷贝
    this->m_NPCName = new char[strlen(m_NPCName) + 1];
    memset(this->m_NPCName,0, strlen(m_NPCName) + 1);
    strcpy(this->m_NPCName, m_NPCName);
}
拷贝构造函数
cpp 复制代码
RolePlayer(const RolePlayer & other) :  m_nHP(other.m_nHP)
{
    //执行深拷贝
    m_NPCName = new char[sizeof(strlen(other.m_NPCName) + 1)];
    strcpy(m_NPCName, other.m_NPCName);
    std::cout << "拷贝构造函数执行成功" << std::endl;
}

修改后两个对象都成功执行析构,且程序无任何报错。

相关推荐
一颗花生米。21 分钟前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式
问道飞鱼21 分钟前
Java基础-单例模式的实现
java·开发语言·单例模式
学习使我快乐0125 分钟前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
通信仿真实验室1 小时前
(10)MATLAB莱斯(Rician)衰落信道仿真1
开发语言·matlab
勿语&1 小时前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
hallo1282 小时前
vscode环境迁移
ide·vscode·编辑器
家有狸花2 小时前
VSCODE驯服日记(三):配置C++环境
c++·ide·vscode
dengqingrui1233 小时前
【树形DP】AT_dp_p Independent Set 题解
c++·学习·算法·深度优先·图论·dp
C++忠实粉丝3 小时前
前缀和(8)_矩阵区域和
数据结构·c++·线性代数·算法·矩阵
ZZZ_O^O3 小时前
二分查找算法——寻找旋转排序数组中的最小值&点名
数据结构·c++·学习·算法·二叉树