深拷贝(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)
按照这种方式直接进行成员变量的初始化等于直接将roleB
的m_NPCName
成员地址指向roleA
的m_NPCName
地址;那么这个时候再执行析构函数:
cpp
//析构函数
~RolePlayer()
{
if (m_NPCName != nullptr) {
delete m_NPCName;
}
std::cout << "析构函数" << std::endl;
}
这个时候在roleA
对象的析构函数执行的时候他的m_NPCName
成员就会被释放,那么这个时候roleB
对象的析构函数执行的时候在执行delete m_NPCName;
时就出现双重释放漏洞
从而产生报错。除此之外还有SetName
函数也是浅拷贝,也会出现相同的结果。
双重释放漏洞
双重释放漏洞是指在程序中释放已经被释放的内存块的情况。这种情况下,同一块内存可能被释放两次或多次,导致程序出现未定义的行为、内存损坏或崩溃。
为了解决这个问题,就应该使用深拷贝来确保每个对象都有自己独立的内存块。这意味着在拷贝构造函数中应该动态分配新的内存,并将原始字符串的内容复制到新的内存空间中。同时,在析构函数中应该释放动态分配的内存。(即拷贝构造函数和SetName
函数都需要执行深拷贝)。
深拷贝
深拷贝是一种拷贝对象的方式,其中对象的成员变量及其所指向的内存都被复制到新对象中。这意味着新对象拥有与原对象相同的值,但是它们的内存空间是相互独立的,即使一个对象被修改也不会影响到另一个对象。
深拷贝通常发生在以下情况下:
-
对象中包含动态分配的内存:如果对象中的成员变量是指针,并且这些指针指向动态分配的内存(例如使用
new
运算符动态分配的内存),则通常需要深拷贝来确保新对象拥有自己独立的内存空间。 -
对象中包含其他对象:如果对象中包含其他对象作为成员变量,并且这些对象也需要进行复制,那么通常需要深拷贝来递归地复制所有相关对象。
-
对象需要完全独立的副本:在某些情况下,即使对象中的成员变量是基本数据类型,但也需要创建一个完全独立的副本,以避免对原始对象的修改影响到新对象。
修改后的代码:
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;
}
修改后两个对象都成功执行析构,且程序无任何报错。