深拷贝必须手动实现拷贝构造函数,因默认为浅拷贝;类含动态资源时需遵循三法则(或五法则),拷贝构造函数签名应为ClassName(const ClassName& other),核心是为指针成员分配新内存并复制内容,推荐用std::vector替代裸指针以自动深拷贝。

深拷贝必须手动写拷贝构造函数
默认拷贝构造函数只做浅拷贝,对指针成员只是复制地址值。如果类里有 new 出来的堆内存(比如 int*、std::string* 或自定义资源句柄),不重写拷贝构造函数就会导致两个对象指向同一块内存,析构时重复 delete —— 直接崩溃或未定义行为。
关键判断点:只要类中存在「需要独占管理的动态资源」,就必须自己写拷贝构造函数 + 赋值运算符 + 析构函数(即“三法则”,C++11 后建议补上移动语义构成“五法则”)。
拷贝构造函数的标准写法和常见错误
签名必须是 ClassName(const ClassName& other),且不能加 explicit(否则无法隐式拷贝初始化)。核心逻辑是:为每个动态成员分配新内存,并逐字节/逐元素复制内容。
- 错误:直接
this->ptr = other.ptr;→ 浅拷贝,后续析构冲突 - 正确:先
this->ptr = new int(*other.ptr);(单个值)或this->ptr = new int[other.size]; std::copy(other.ptr, other.ptr + other.size, this->ptr);(数组) - 遗漏:没同步复制长度、容量、状态标志等辅助成员(如
size、capacity),会导致访问越界或逻辑错乱 - 隐患:没做空指针检查(
other.ptr可能为nullptr),new失败没处理异常(建议用std::vector替代裸数组来规避)
用 std::vector 替代裸指针能自动深拷贝
如果你的“动态数组”本质是存储连续数据,std::vector 是更安全的选择:它的拷贝构造函数已实现深拷贝,且自动管理内存生命周期。
立即学习“C++免费学习笔记(深入)”;
示例对比:
class BadString {
char* data;
size_t len;
public:
BadString(const char* s) : len(strlen(s)) {
data = new char[len + 1];
strcpy(data, s);
}
// 必须手写深拷贝
BadString(const BadString& other) : len(other.len) {
data = new char[len + 1];
strcpy(data, other.data); // 复制内容,不是地址
}
~BadString() { delete[] data; }
};
class GoodString {
std::vector data;
public:
GoodString(const char* s) : data(s, s + strlen(s) + 1) {}
// 无需写拷贝构造函数:vector 默认就是深拷贝
};
注意:std::vector 拷贝开销是 O(n),但省去了手动内存管理和出错风险;若性能极端敏感且确定只读共享,才考虑引用计数或 std::shared_ptr,但那就不是深拷贝了。
调试深拷贝问题的典型现象和验证方法
最常出现的错误现象是程序在第二次析构时崩在 delete 或 free,错误信息类似:double free or corruption (fasttop) 或 Access violation reading location 0xFEEEFEEE(Windows 调试堆标记)。
验证是否真深拷贝,最简单的方法是:
- 构造对象 A,修改其内部数据(如
A.data[0] = 'X') - 用 A 构造 B(
GoodString B = A;) - 再修改 A.data[0],检查 B.data[0] 是否不变 —— 不变才是深拷贝成功
- 加日志或断点,在拷贝构造函数里打印
this和&other的地址,确认data成员地址不同
真正麻烦的不是写错,而是忘了写——尤其当类初期只有栈成员,后来加了指针却没补全三法则。每次添加裸指针成员,都该立刻检查拷贝/赋值/析构是否同步更新。









