# 深拷贝与浅拷贝
面试官问:"深拷贝和浅拷贝有什么区别?什么时候必须用深拷贝?"
这题看起来简单,但很多人只能说出"浅拷贝复制指针,深拷贝复制内容",问到内存模型层面的原因、什么场景会出问题、怎么和智能指针配合,就答不上来了。关键是从内存布局的角度理解两者的本质差异。
# 简要回答
浅拷贝:只复制对象的成员变量值。对于指针成员,复制的是地址本身,两个对象的指针指向同一块堆内存。
深拷贝:为指针成员重新分配内存并复制内容,两个对象各自拥有独立的堆内存,互不影响。
核心问题:浅拷贝导致两个对象共享同一块堆内存,任何一个对象析构时 delete 这块内存,另一个对象的指针就变成悬垂指针——再次 delete 就是双重释放,程序崩溃。
# 详细回答
编译器默认生成的拷贝构造函数是浅拷贝
编译器自动生成的拷贝构造函数和赋值运算符,做的是逐成员复制(memberwise copy)。对于 int、double 这些值类型没问题,但对于指针成员,复制的只是地址值——两个对象的指针指向同一块内存。
浅拷贝的致命问题
当类管理动态内存(new 出来的资源)时,浅拷贝会导致:
- 双重释放:两个对象析构时都
delete同一块内存,程序崩溃 - 悬垂指针:一个对象析构后,另一个对象的指针指向已释放的内存,访问就是未定义行为
- 意外修改:通过一个对象修改数据,另一个对象的数据也被改了
深拷贝的实现
当类包含指针成员且管理动态内存时,必须手动实现:拷贝构造函数、赋值运算符、析构函数——这就是 C++ 的 Rule of Three。拷贝构造和赋值运算符中,为指针成员 new 新内存,把原对象指向的内容复制过来。这样两个对象各自拥有独立的内存,析构时各自释放,互不干扰。
现代 C++ 的替代方案
C++11 之后,用 unique_ptr 或 shared_ptr 管理动态内存,可以避免手写深拷贝。unique_ptr 禁止拷贝(只能移动),从根本上避免共享问题;shared_ptr 用引用计数自动管理生命周期,多个对象可以安全共享同一块内存。

# 知识拓展
面试官可能追问:
Q1: 什么时候必须写深拷贝?
只要类里有裸指针(T*)并且这个指针指向的内存是类自己 new 出来的,就必须写深拷贝。判断标准很简单:如果你写了析构函数里有 delete,那就一定要写拷贝构造和赋值运算符(Rule of Three)。如果类里只有值类型成员或者智能指针,编译器默认的浅拷贝就够用。
Q2: STL 容器存自定义对象时,拷贝语义有什么坑?
STL 容器是值语义,push_back 会触发拷贝构造。如果你的类有裸指针但没写深拷贝,容器里的对象和外面的对象共享内存,容器扩容时旧元素析构会释放内存,新元素的指针就悬空了——直接崩溃。解决方案:要么实现深拷贝,要么用智能指针管理资源,要么存指针而不是对象。
Q3: 写时复制(COW)是什么?和深拷贝什么关系?
写时复制是一种优化策略:拷贝时先做浅拷贝(共享内存),只有当某个对象要修改数据时,才真正做深拷贝。用引用计数跟踪有多少对象共享同一块内存,修改前检查计数——如果大于 1,先复制一份再改。早期的 std::string 实现就用了 COW,但因为多线程下引用计数的原子操作开销太大,C++11 之后标准库不再使用这种策略。
Q4: 移动语义和深拷贝是什么关系?
移动语义是 C++11 引入的第三种选择——既不是浅拷贝(共享资源),也不是深拷贝(复制资源),而是转移资源所有权。移动构造函数把源对象的指针"偷过来",然后把源对象的指针置空。代价从 O(n) 的深拷贝降到 O(1) 的指针赋值。当你不再需要源对象时(比如函数返回临时对象),移动比深拷贝高效得多。
← 如何禁止一个类被继承 this指针的原理 →
评论
验证登录状态...