浅拷贝仅复制指针值导致野指针,深拷贝需在拷贝构造函数中new分配内存并拷贝数据,且须同步实现析构函数和拷贝赋值运算符以遵循三/五法则。

拷贝构造函数里不手动分配内存就是浅拷贝
只要类里有指针成员(比如 int*、char* 或 std::string*),默认生成的拷贝构造函数只会复制指针值,两个对象指向同一块堆内存。一旦其中一个析构时 delete 了,另一个再访问就是野指针 —— 这是典型的浅拷贝问题。
深拷贝必须在拷贝构造函数里:① 用 new(或 malloc)重新分配内存;② 把原对象的数据逐字节或按逻辑拷贝过去;③ 确保后续析构能安全释放两份独立内存。
写深拷贝要同步改掉析构和赋值运算符
只改拷贝构造函数是不够的。C++ 的“三法则”(现在叫“五法则”)要求:只要需要自定义拷贝构造,几乎一定也要自定义析构函数和拷贝赋值运算符 operator=,否则资源管理会错乱。
常见错误现象:
• 对象被赋值后,原对象析构时把新对象的内存也 delete 了
• 临时对象返回时触发拷贝构造,但析构又删了一次
• 多次 delete 同一地址,程序崩溃或静默损坏
- 析构函数里必须
delete自己的指针,且判空(防止重复释放) -
operator=要先检查自赋值(if (this == &other)),再释放旧资源,再深拷贝新资源 - C++11 起建议加上移动构造和移动赋值,否则
std::vector等容器扩容时可能意外触发深拷贝
用 std::vector 或 std::string 替代裸指针更安全
裸指针深拷贝容易漏掉边界、长度、容量等细节。例如 char* 字符串要小心 strlen + 1、末尾 <p>裸指针深拷贝容易漏掉边界、长度、容量等细节。例如 <code>char* 字符串要小心 strlen + 1、末尾 \0;动态数组要记清元素个数和类型大小。
现代 C++ 推荐直接用 RAII 容器:
class Buffer {
std::vector<int> data_; // 自动管理内存,拷贝构造默认就是深拷贝
public:
Buffer(const Buffer& other) : data_(other.data_) {} // 编译器生成的就对了
};
</int>
这样既不用手写 new/delete,也不会忘掉 operator=。只有在性能极端敏感、或必须对接 C API 时,才考虑裸指针加手动深拷贝。
调试深拷贝问题的关键检查点
深拷贝出错往往不报编译错误,而是运行时崩溃或数据错乱。排查时盯住这几个地方:
- 拷贝构造函数里有没有漏掉某个指针成员?比如新增了
double* weights_却没在拷贝里处理 - 分配内存时用了
new int[size],但拷贝时用了memcpy(ptr, other.ptr, size)—— 这里单位错了,应该是size * sizeof(int) - 类里有
const成员或引用成员,会导致编译器不生成默认拷贝构造,但你忘了显式定义它 - 使用
std::auto_ptr(已弃用)或自定义智能指针时,拷贝语义可能是转移而非复制,行为不符合预期
最容易被忽略的是:深拷贝逻辑写对了,但忘了把拷贝构造函数声明为 explicit(如果本意不希望隐式转换),或者没加 = default / = delete 控制其他构造方式。这类问题不会立刻暴露,但会在复杂调用链中突然引发双重释放。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











