深拷贝需手动实现,因默认拷贝仅复制指针值导致重复释放;应优先用raii容器替代裸指针,若必须用则在拷贝构造函数和赋值运算符中new内存并逻辑复制,同时禁用拷贝与移动以彻底防止资源失控。

深拷贝必须手动实现,编译器默认只做浅拷贝
当你类里有 new 出来的指针、std::unique_ptr 以外的裸指针、或持有系统资源(如文件描述符、socket 句柄),编译器生成的默认拷贝构造函数和赋值运算符只会复制指针值——两个对象指向同一块内存,析构时重复 delete 就崩。
实操建议:
- 只要类中有动态分配的资源,就禁用默认拷贝,显式定义深拷贝逻辑
- 优先用 RAII 容器替代裸指针:比如用
std::vector替int*,用std::string替char*,它们自带正确深拷贝 - 若必须用裸指针,拷贝构造函数里要
new新内存,并逐字节或按逻辑复制内容(不是memcpy乱拷) - 别忘了同步实现深拷贝版的
operator=,并处理自赋值(if (this == &other) return *this;)
禁用拷贝构造函数的三种写法及区别
禁用目的很明确:防止意外复制导致资源管理失控。但不同写法影响接口可用性和错误提示质量。
常见错误现象:代码能编译通过,但运行时崩溃;或者 IDE 没报错,却在调用 std::vector.push_back() 时链接失败(因为模板实例化需要拷贝)。
立即学习“C++免费学习笔记(深入)”;
实操建议:
- 推荐写法:
MyClass(const MyClass&) = delete;—— 最清晰,编译期直接报错,错误信息含“deleted function”关键词 - C++11 前模拟禁用:把拷贝构造函数声明为
private且不定义,但现代项目别用,IDE 和编译器提示差,还可能被友元绕过 - 别只禁构造函数不禁赋值:
MyClass& operator=(const MyClass&) = delete;必须一起加,否则赋值仍可发生
移动语义开启后,禁用拷贝 ≠ 禁止所有复制行为
如果类支持移动(即定义了移动构造函数和移动赋值),而你只禁用了拷贝,那 std::move(obj) 还是能“转移”资源——这本身没问题,但容易误以为“完全禁止了复制”,结果在容器操作中触发移动而非报错。
使用场景:比如网络连接类、数据库句柄类,资源不可复制也不可转移,就必须同时禁用拷贝和移动。
实操建议:
- 真要彻底禁止任何复制/转移行为,四法则全禁:
= delete拷贝构造、拷贝赋值、移动构造、移动赋值 - 禁用移动后,类无法放入需要移动的容器(如
std::vector的扩容),得改用std::list或确保容量足够避免重分配 - 检查是否被隐式调用:比如函数参数传值、返回局部对象、
std::make_shared<myclass>(...)</myclass>都会尝试拷贝或移动
std::shared_ptr 能解决深拷贝问题?不,它解决的是共享所有权
有人以为用 std::shared_ptr<t></t> 包裹成员,就能“自动深拷贝”,这是典型误解。std::shared_ptr 的拷贝是浅的——引用计数+指针复制,底层 T 对象仍是同一份。
性能影响:频繁拷贝 std::shared_ptr 开销小(只是原子增减计数),但若你以为它帮你做了深拷贝,后续修改可能污染其他对象状态。
实操建议:
- 要用深拷贝,还是得在拷贝构造函数里 new 一个新
T,再用std::shared_ptr<t>(new T(other.ptr_.get()))</t>或更安全的std::make_shared<t>(*other.ptr_)</t> - 如果
T本身不可拷贝(比如含= delete),那连这种深拷贝都做不了,只能换设计 - 注意
std::shared_ptr不解决循环引用,该加std::weak_ptr还得加
最易被忽略的一点:深拷贝逻辑必须和析构函数严格对称——new 多少次,delete 就得多少次;开多少个 socket,close 就得多少次。漏掉任意一端,就是悬垂指针或资源泄漏,而且往往只在特定路径下暴露。









