浅拷贝的本质是地址复制而非数据复制,即仅复制指针值使多个对象共享同一内存,易导致double free或悬空指针;安全做法是用std::shared_ptr替代裸指针实现带引用计数的浅拷贝语义。

浅拷贝的本质是地址复制,不是数据复制
当你用指针实现浅拷贝时,实际只是把原对象中指针变量的值(即内存地址)赋给新对象的对应指针。两个指针指向同一块堆内存,修改任一对象的数据,另一方立刻可见——这不是bug,而是浅拷贝的定义行为。
典型场景:自定义类中含 int* 成员,且未显式定义拷贝构造函数或赋值运算符。此时编译器生成的默认版本就是逐成员复制,对指针而言就是地址拷贝。
常见错误现象:double free 或 use-after-free ——析构时两个对象都试图 delete 同一块内存;或者一个对象提前释放内存,另一个还在读写它。
- 必须确保原始对象生命周期长于所有浅拷贝对象,否则极易悬空指针
- 如果类中同时管理资源(如文件句柄、socket),仅靠指针浅拷贝无法同步管理,需额外机制
- 浅拷贝不等于“不安全”,在只读共享、观察者模式等场景下是合理设计
手动实现浅拷贝构造函数要避免隐式深拷贝陷阱
很多人误以为写了拷贝构造函数就一定是深拷贝。其实只要没对指针做 new + memcpy,而直接用 other.ptr 赋值,就是浅拷贝。
立即学习“C++免费学习笔记(深入)”;
class Data {
int* ptr;
public:
Data(int val) : ptr(new int(val)) {}
// 这是浅拷贝构造函数
Data(const Data& other) : ptr(other.ptr) {} // ← 关键:没 new,只复制地址
~Data() { delete ptr; }
};注意:上面代码虽能编译运行,但析构时会崩溃。原因在于两个对象的 ptr 指向同一地址,delete 执行两次。
- 若真要浅拷贝,析构函数不能无条件
delete ptr,得加引用计数或标记位 - 更常见的做法是把浅拷贝逻辑和资源所有权分离,比如用
std::shared_ptr<int>替代裸指针 - 不要在拷贝构造函数里调用
new,否则就变成深拷贝了——除非你明确需要独立副本
用 std::shared_ptr 实现安全的浅拷贝语义
裸指针浅拷贝难维护,而 std::shared_ptr 把地址共享 + 引用计数封装在一起,天然支持浅拷贝语义且线程安全(控制块操作)。
class SafeData {
std::shared_ptr<int> ptr;
public:
SafeData(int val) : ptr(std::make_shared<int>(val)) {}
SafeData(const SafeData& other) : ptr(other.ptr) {} // ← 浅拷贝:引用计数+1
int get() const { return *ptr; }
void set(int v) { *ptr = v; }
};这时两个 SafeData 对象的 ptr 共享同一块内存,修改互相可见,但不会 double free —— shared_ptr 的析构会自动检查引用计数,仅当降为 0 时才 delete。
-
std::shared_ptr的拷贝开销很小(原子增减引用计数),比深拷贝廉价得多 - 若需只读共享,可用
std::shared_ptr<const int>防止意外修改 - 避免循环引用:两个对象互相持有对方的
shared_ptr,会导致内存泄漏
什么时候不该用浅拷贝?
浅拷贝只适合「共享状态」场景。一旦对象需要独立状态、或存在写入竞争,就必须切换到深拷贝或值语义。
典型反例:容器类(如 MyVector)若用浅拷贝,两个实例共用同一片 int* 数组,push_back 可能触发 realloc,导致一个实例的指针悬空,另一个仍用旧地址访问。
- 类中含多个指针、或指针指向动态大小数据(如字符串、数组)时,浅拷贝极易出错
- 多线程环境下,即使只读,也建议用
const shared_ptr<T>明确意图,避免意外写入 - 接口设计上,如果函数参数声明为
const Data&,内部不应假设其指针可长期有效——生命周期由调用方控制
裸指针浅拷贝的边界非常窄,多数时候应该用 shared_ptr 或直接改用值语义(如 std::vector<int>)。真正需要手动管理指针浅拷贝的场景,往往意味着你在构建底层基础设施,而不是业务逻辑。


















