大二学生血泪教训:C++深浅拷贝详解 面试必问
作为C++初学者,当你开始使用指针和动态内存分配时,“深拷贝”和“浅拷贝”是必须跨越的一道坎。不理解它们,轻则程序崩溃,重则内存泄漏。让我带你彻底搞懂这两个概念的区别、应用场景以及如何正确实现它们。
概述
浅拷贝因为初始化或者赋值一个对象时,会把指针也复制过去从而导致析构函数重复delete,导致世界末日
通过深拷贝用不同的内存空间,这样就不会重复delete掉两次了
一、什么是拷贝
在C++中,对象拷贝主要发生在两种场景:
1.使用一个对象初始化另一个对象,说人话就是新建一个对象等于另一个对象
Myclass obj2 = obj1 (这里调用了拷贝构造函数)
2.将一个对象赋值给另一个已存在的对象,也就是令一个对象和另一个对象相等
obj2 = obj1 (这里调用了赋值运算符 operator= )
默认情况下,编译器会自动生成拷贝构造函数和赋值运算符。
它们逐个成员地复制对象的内容。对于基本数据类型(int
, float
, char
等),这种复制是直接的值拷贝,这一部分是没有问题的。问题就出在当我们的类成员包含指针,并且这些指针指向动态分配的内存(堆内存)时。
二、浅拷贝潜伏危机
浅拷贝只复制对象中的内存地址,而不复制这个指针所指的那块内存。结果是,拷贝后的对象和原对象的指针成员指向同一块堆内存
刚才我们提到的默认情况下, 就是编译器的浅拷贝。
我们来看一下运行结果:
构造函数调用: 分配内存 @0x7ffee3d05700
对象地址: 0x7ffee3d056f0, data指向: 0x7ffee3d05700
对象地址: 0x7ffee3d056e0, data指向: 0x7ffee3d05700 // obj2.data 和 obj1.data 指向同一个地址!
析构函数调用: 释放内存 @0x7ffee3d05700 // obj2 释放了内存
析构函数调用: 释放内存 @0x7ffee3d05700 // obj1 试图再次释放同一块内存 -> 崩溃!!!
其中的问题所在就是,obj1和obj2中的data指向了同一个堆内存空间,这就导致在delete中重复删除了两次
当时我在这里有一个疑问,就是前面加了if 该地址 != nullptr 再释放为什么不可以解决呢
在delete掉第一个obj中的data时,该内存确实被释放了,但是obj2中data仍然存放了 0x7ffee3d05700,所以判断不为nullptr,所以就会再delete一次这个内存空间
三、深拷贝实现安全保障
深拷贝可以给指针成员重新分配堆内存,并将原对象指针指向的内容(不仅仅是地址)复制到新分配的内存中。这样,拷贝后的对象和原对象拥有完全独立的数据副本,不会互相影响
那我们什么时候需要深拷贝呢?
1.类成员包含指向堆内存的原始指针
2.类管理着需要独占性控制的资源(如文件句柄、网络连接等)
什么时候可以浅拷贝呢?
1.类成员只包含基本数据类型 (`int`, `float`, `char` 等)。
2.成员包含本身能正确管理拷贝的类对象 (例如 std::string
, std::vector
)。成员包含本身能正确管理拷贝的类对象 (例如 `std::string`, `std::vector`)
3.设计本身就是需要共享资源
四、现代C++可以避免手动深拷贝
1.使用标准库容器 (std::vector
, std::string
等)
2.使用智能指针 (std::unique_ptr
, std::shared_ptr
)