浅拷贝仅复制指针导致共享内存,深拷贝需手动分配并复制数据;拷贝构造函数在对象初始化、值传递参数、返回局部对象时调用;必须同时重载拷贝构造函数和operator=以避免行为不一致,并注意自赋值、异常安全及元信息同步。

浅拷贝只是复制指针,深拷贝才真正复制数据
默认的拷贝构造函数和赋值运算符执行的是浅拷贝——它把对象里的每个字节原样复制过去。如果类里有 char*、int* 或其他裸指针成员,浅拷贝后两个对象会指向同一块堆内存。一旦其中一个析构时调用 delete,另一方再访问就是野指针;若两次析构,还会触发 double free 错误。
深拷贝必须在拷贝构造函数和 operator= 中手动分配新内存,并把原始数据逐字节或按逻辑复制过去。这是资源管理的基本守则,不写就会出问题。
拷贝构造函数什么时候被调用
它不是只在 A b = a; 这种写法里触发。以下场景都会调用:
- 用一个已存在对象初始化新对象:
A b(a);或A b = a; - 函数传参时以值传递方式接收对象:
void func(A x) { ... }调用时func(a) - 函数返回局部对象(且未被编译器优化掉):
A create() { A x; return x; }
注意:现代编译器普遍启用 RVO/NRVO 优化,可能跳过拷贝构造。但逻辑上仍需正确实现,否则关掉优化或换编译器就崩。
立即学习“C++免费学习笔记(深入)”;
必须同时重载拷贝构造函数和 operator=
只写拷贝构造函数而忽略赋值运算符,或反过来,会导致行为不一致。比如:
A a; A b; b = a; // 调用 operator=,若没重载,就是浅拷贝 A c = a; // 调用拷贝构造函数,若重载了,是深拷贝
这种不对称极易引发隐性 bug。标准做法是遵循“三法则”(C++11 后为“五法则”):只要写了析构函数、拷贝构造函数、拷贝赋值运算符中的任一个,另外几个通常也得自己写。
常见疏漏点:
-
operator=忘记处理自赋值:a = a;会导致先delete再访问已释放内存 - 拷贝构造函数里调用
new失败未检查,抛出异常后对象处于半构造状态 - 没有把源对象的非指针成员(如
size_t len)一并复制,导致新对象元信息错乱
用 std::string 和 std::vector 可以绕过手写深拷贝
它们内部已经实现了正确的深拷贝语义。如果你的类里原来用 char* 存字符串,换成 std::string;用 int* + size_t 管理数组,换成 std::vector,那么默认生成的拷贝构造函数就能安全工作。
但这不等于可以忽视原理——当涉及文件句柄、socket、shared memory 等系统资源时,依然要手动管理。而且,有些老项目或嵌入式环境禁用 STL,这时深拷贝逻辑逃不掉。
真正容易被忽略的是:即使用了 std::vector,若类中还有裸指针成员(比如缓存用的 float* m_cache),那依然得自己写深拷贝。别以为加了 STL 就万事大吉。











