C++ 怎么实现深拷贝 C++ 拷贝构造函数与赋值运算符重载【核心】

24次阅读

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

C++ 怎么实现深拷贝 C++ 拷贝构造函数与赋值运算符重载【核心】

深拷贝必须手动实现,编译器默认的拷贝是浅的

当你类里有指针成员(比如 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::vectorstd::unique_ptr 等 RAII 容器替代裸指针,它们自带正确的深拷贝语义,能大幅减少手动管理出错的概率。

text=ZqhQzanResources