深拷贝必须手动实现,编译器默认只做浅拷贝;类含指针等动态资源时,需显式定义拷贝构造函数和赋值运算符,正确分配内存并复制内容,同时处理自赋值、异常安全及资源释放,推荐优先使用std::vector等RAII容器。

深拷贝必须手动实现,编译器默认只做浅拷贝
当你在 C++ 中定义一个类,且该类内部含有指针成员(比如 int*、std::string* 或自定义对象指针),编译器生成的默认拷贝构造函数和赋值运算符只会复制指针的值——也就是地址本身。结果是两个对象指向同一块堆内存,后续任意一方释放或修改,都会导致另一方访问野指针或数据错乱。
所以只要类里有动态分配的资源(堆内存、文件句柄、socket 描述符等),就必须显式定义:
-
MyClass(const MyClass& other)(拷贝构造函数) -
MyClass& operator=(const MyClass& other)(拷贝赋值运算符)
拷贝构造函数里要 new 出新内存并逐字节复制内容
关键不是“复制指针”,而是“复制指针所指的内容”。假设类中有一个 int* data_ 和 size_t size_:
class Buffer {
int* data_;
size_t size_;
public:
Buffer(const Buffer& other) : size_(other.size_) {
data_ = new int[size_]; // 分配新内存
std::copy(other.data_, other.data_ + size_, data_); // 复制内容
}
};
注意三点:
- 必须先初始化
size_,再用它决定new多大空间,否则可能读取未初始化成员 - 不能写成
data_ = other.data_,那是浅拷贝 - 如果
other.data_是nullptr,std::copy会崩,得加空指针检查
operator= 要处理自赋值和旧资源释放
拷贝赋值比拷贝构造更复杂:目标对象可能已有有效资源,还可能出现 a = a 这种自赋值。
常见写法是「先释放旧资源,再分配新资源」,但这样无法应对自赋值(释放后自己就没了)。稳妥做法是「先分配、再交换、最后清理」,或者显式判断:
Buffer& operator=(const Buffer& other) {
if (this == &other) return *this; // 自赋值检查
<pre class="brush:php;toolbar:false;">delete[] data_; // 释放当前资源
size_ = other.size_;
data_ = new int[size_];
std::copy(other.data_, other.data_ + size_, data_);
return *this;}
容易踩的坑:
- 漏掉
if (this == &other),自赋值会导致delete[] data_后又用other.data_拷贝,访问已释放内存 - 异常不安全:如果
new int[size_]抛出std::bad_alloc,原对象已被delete[],变成半残状态 - 没写析构函数,或析构里没
delete[] data_,必然内存泄漏
现代 C++ 更推荐用 RAII 容器替代裸指针
手写深拷贝逻辑繁琐易错。实际项目中,优先用 std::vector、std::string、std::unique_ptr 等 RAII 类型:
class Buffer {
std::vector<int> data_;
public:
Buffer(const Buffer& other) = default; // 编译器生成的拷贝构造已正确深拷贝 vector
Buffer& operator=(const Buffer& other) = default;
};</int>
这时你完全不用操心深拷贝——因为 std::vector 内部已经实现了。只有当你必须管理裸指针(比如对接 C API、性能敏感场景、或封装底层资源)时,才需要自己写。
真正麻烦的是混合资源:比如一个类同时持有 int*、FILE* 和 std::string。这时候每个资源类型都要单独处理释放和拷贝逻辑,稍有遗漏就会崩溃或泄漏。别低估这种组合的维护成本。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











