深拷贝必须手动实现,因默认拷贝为浅拷贝,指针成员仅复制地址,易致野指针;拷贝构造无需处理自赋值,但需确保异常安全;赋值运算符必须用拷贝-交换法兼顾自赋值与异常安全;遵循三/五法则,并优先使用RAII容器。

深拷贝必须手动实现,编译器默认的拷贝是浅的
当你类里有指针成员(比如 int*、std::string* 或自定义资源句柄),编译器生成的默认拷贝构造函数和 operator= 只会复制指针值,导致两个对象指向同一块内存。一旦其中一个析构释放了内存,另一个再访问就是野指针 —— 这是典型的未定义行为,常见表现为程序崩溃或随机数据错误。
深拷贝的核心动作就一条:为每个指针成员分配新内存,并把原对象的数据完整复制过去。这不能靠编译器自动完成,必须你亲自写逻辑。
拷贝构造函数要检查 self-assignment 吗?不需要
拷贝构造函数(A(const A& other))的参数是 const 引用,且它只在对象「诞生时」被调用(如 A b = a;、func(a) 传参),不可能出现自己构造自己,所以无需判 self-assignment。
但要注意:如果构造函数内部调用了可能抛异常的资源分配(如 new),记得用 RAII 或 try-catch 保护,避免构造中途失败导致资源泄漏。
立即学习“C++免费学习笔记(深入)”;
示例关键片段:
A(const A& other) : ptr_(nullptr), size_(other.size_) {
if (other.ptr_) {
ptr_ = new int[other.size_];
std::copy(other.ptr_, other.ptr_ + other.size_, ptr_);
}
}
赋值运算符重载必须处理 self-assignment 和异常安全
operator= 是唯一可能 self-assign 的场景(如 a = a;),不检查会导致先 delete ptr_ 再 new 同一块地址,然后 std::copy 读已释放内存 —— 直接崩。
更关键的是异常安全:如果 new 失败抛出 std::bad_alloc,原对象不能处于无效状态(比如 ptr_ 已 delete 但没重建)。推荐用「拷贝-交换」惯用法(copy-and-swap),天然支持强异常安全且自动处理 self-assign:
- 以值传递接收参数(触发拷贝构造,即深拷贝)
- 与临时对象交换成员(
swap(*this, other),交换是无异常的) - 临时对象离开作用域时析构,释放旧资源
示例:
A& operator=(A other) { // 注意:不是 const A&,是值传递
swap(*this, other);
return *this;
}
void swap(A& lhs, A& rhs) noexcept {
std::swap(lhs.ptr_, rhs.ptr_);
std::swap(lhs.size_, rhs.size_);
}
别忘了三法则(C++11 后是五法则)
只要你写了拷贝构造、拷贝赋值、析构函数中的任一个,几乎肯定也要写另外两个(三法则)。C++11 加入移动语义后,若涉及资源管理,还应显式定义或 =default/=delete 移动构造和移动赋值(五法则)。
容易忽略的点:
- 析构函数里没
delete[] ptr_或漏掉某个指针 → 内存泄漏 - 拷贝构造中忘了初始化所有成员(尤其是非指针成员)→ 值不确定
- 赋值函数返回类型不是
A&或没return *this→ 链式赋值(a = b = c)失效 - 把
operator=声明成const A&参数却没加const修饰函数体 → 编译不过
现代 C++ 更推荐用 std::vector、std::unique_ptr 等 RAII 容器替代裸指针,它们自带正确的深拷贝语义,能大幅减少手动管理出错的概率。









