浅拷贝是编译器默认行为,但含裸指针时会导致野指针或double free;深拷贝需手动实现拷贝构造函数和赋值运算符,为指针成员重新分配内存并复制内容;现代C++推荐用std::string、std::vector等RAII容器替代裸指针。

浅拷贝是编译器默认行为,但往往不是你想要的
当你没写拷贝构造函数或赋值运算符时,C++ 会自动生成一个——它对每个成员做逐字节复制。对 int、double 这类内置类型没问题,但一旦类里有指针(比如 char* 或 std::string*),问题就来了:两个对象的指针指向同一块堆内存。一个对象析构时 delete 了,另一个再访问就是野指针;或者两次 delete 同一块内存,直接 double free 崩溃。
深拷贝必须手动实现拷贝构造函数和赋值运算符
核心原则:为指针成员重新分配内存,并把原数据复制过去。不能只复制指针值。
常见错误现象:
- 程序运行一会儿就
Segmentation fault - 释放对象后,另一个对象的字符串变成乱码或空
- 用
valgrind检测到Invalid read或double free
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 只要类中有裸指针(
char*、int*等),就必须显式定义拷贝构造函数和operator= - 两者逻辑高度相似:先释放目标已有资源(仅赋值运算符需要),再申请新内存,再复制内容
- 别忘了在析构函数里
delete指针,且判空避免重复释放
示例(简化版):
class String {
char* data_;
size_t len_;
public:
String(const char* s) : len_(s ? strlen(s) : 0) {
data_ = new char[len_ + 1];
if (s) strcpy(data_, s);
else data_[0] = '\0';
}
<pre class="brush:php;toolbar:false;">// 深拷贝构造函数
String(const String& other) : len_(other.len_) {
data_ = new char[len_ + 1];
strcpy(data_, other.data_);
}
// 深拷贝赋值运算符(含自赋值检查)
String& operator=(const String& other) {
if (this == &other) return *this;
delete[] data_; // 先释放旧资源
len_ = other.len_;
data_ = new char[len_ + 1];
strcpy(data_, other.data_);
return *this;
}
~String() { delete[] data_; }};
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
现代 C++ 更推荐用 RAII 容器替代裸指针
手写深拷贝容易出错,且不满足“零开销抽象”精神。标准库容器(如 std::string、std::vector)已经实现了正确的深拷贝语义,它们的拷贝构造和赋值都是深的,你不用管底层怎么分配内存。
使用场景:
- 字符串操作:直接用
std::string,别自己管理char* - 动态数组:用
std::vector<t></t>,不是T*+size_t - 需要共享所有权?考虑
std::shared_ptr,它自带引用计数,不是深拷贝但更安全
性能影响:容器深拷贝确实有开销(内存分配 + 复制),但这是明确可预期的;裸指针浅拷贝看似快,实际引发崩溃或数据竞争,代价更高。
移动语义能绕过深拷贝,但不等于可以忽略拷贝逻辑
C++11 后,如果对象很大(比如含 megabytes 的 std::vector),频繁深拷贝会拖慢性能。这时可以加移动构造函数和移动赋值运算符,把资源“偷”过来,避免复制。
但注意:
- 移动后原对象处于有效但未指定状态(比如
std::vector移动后为空),不能假设它还能用 - 移动语义不取代深拷贝逻辑——你仍需保证拷贝构造/赋值是深的,除非你明确禁用(
= delete) - 编译器不会自动为你生成移动函数;写了拷贝函数,移动函数也不会被隐式删除,必须显式定义或
= default
最容易被忽略的一点:深拷贝的正确性,和你是否写了移动函数无关。哪怕全用移动,只要拷贝函数存在且被调用(比如传值参数、std::vector::push_back 非右值),它就必须是深的。

















