
承接上文上一节学习析构函数掌握对象销毁时资源释放并且了解C三法则只要手写析构一般就需要手写拷贝构造、拷贝赋值。本节学习拷贝构造函数对象发生复制时自动调用重点拆解新手最致命bug浅拷贝带来的双重释放崩溃学会深拷贝实现安全的对象复制。学习说明普通拷贝编译器默认生成的拷贝构造只是简单按字节复制对象内存也就是浅拷贝。 如果类内部持有堆内存指针浅拷贝会让两个对象的指针指向同一块堆内存。两个对象先后销毁时析构函数会对同一块内存执行两次delete程序直接崩溃。 深拷贝则是重新开辟一块独立堆内存复制数据保证每个对象拥有自己独立资源从根源解决双重释放问题。本节学习目标掌握拷贝构造函数语法、触发时机与函数特征理解默认拷贝构造 浅拷贝看懂浅拷贝底层内存行为复现浅拷贝经典bug双重释放导致程序崩溃掌握深拷贝完整实现方案学会堆内存独立分配理解C三法则完整含义建立资源管理类编码规范一、拷贝构造基础语法与触发时机拷贝构造核心特征属于构造函数的重载参数必须是本类的const引用const ClassName不能是值传递对象复制时自动调用用一个已有对象初始化新对象不手动编写拷贝构造时编译器自动生成默认拷贝构造执行浅拷贝#include iostream #include string using namespace std; class Person { private: string m_name; public: Person(string name) : m_name(name) { cout 构造函数 m_name endl; } // 拷贝构造函数 Person(const Person p) { m_name p.m_name; cout 拷贝构造执行 m_name endl; } ~Person() { cout 析构函数 m_name endl; } }; int main() { Person p1(张三); Person p2 p1; // 拷贝构造触发用p1复制出新对象p2 return 0; }运行输出构造函数张三 拷贝构造执行张三 析构函数张三 析构函数张三触发拷贝构造的常见场景使用已有对象初始化新对象Person p2(p1);/Person p2 p1;函数传参值传递传入类对象会拷贝生成临时对象函数返回以值方式返回类对象注意对象之间赋值p2 p1不会调用拷贝构造调用的是拷贝赋值运算符后续章节补充错误示例拷贝构造参数使用值传递无限递归#include iostream #include string using namespace std; class Person { private: string m_name; public: // 错误写法参数是值传递 Person(Person p) { m_name p.m_name; } }; // 调用Person p2 p1时需要把p1拷贝给形参p再次触发拷贝构造无限递归 // 编译报错error: invalid constructor; you probably meant ‘Person(const Person)’二、浅拷贝默认拷贝构造带来的双重释放崩溃重点当类内部有new出来的堆内存指针编译器默认浅拷贝只是单纯复制指针的值不会重新分配堆内存。两个对象指针指向同一块堆内存。对象销毁析构函数先后执行delete[]同一块内存释放两次 → 程序崩溃。#include iostream using namespace std; class Buffer { private: char* m_buf; public: Buffer(int size) { m_buf new char[size]; cout 构造分配堆内存 endl; } ~Buffer() { delete[] m_buf; m_buf nullptr; cout 析构释放堆内存 endl; } // 没有手写拷贝构造编译器自动生成浅拷贝 }; int main() { Buffer b1(100); Buffer b2 b1; // 浅拷贝b1.m_buf 和 b2.m_buf 指向同一个堆地址 return 0; // 函数结束先销毁b2执行delete[]再销毁b1再次delete[]双重释放程序崩溃 }程序崩溃输出构造分配堆内存 析构释放堆内存 析构释放堆内存 double free or corruption (out): 0x... 已放弃 (核心已转储)三、深拷贝完整实现方案工程标准写法解决思路手写拷贝构造函数在拷贝构造内部重新new一块独立堆内存再把源对象的数据复制过去。两个对象各自持有自己的堆内存析构时释放各自内存不会冲突。#include iostream #include cstring using namespace std; class Buffer { private: char* m_buf; int m_size; public: Buffer(int size) : m_size(size) { m_buf new char[m_size]; memset(m_buf, 0, m_size); // 内存初始化为0 cout 构造分配堆内存 endl; } // 深拷贝拷贝构造 Buffer(const Buffer other) { // 顺序很重要先分配新内存再复制数据 m_size other.m_size; m_buf new char[m_size]; // 拷贝源内存的数据 memcpy(m_buf, other.m_buf, m_size); cout 深拷贝构造新建独立堆内存 endl; } ~Buffer() { delete[] m_buf; m_buf nullptr; cout 析构释放堆内存 endl; } }; int main() { Buffer b1(100); Buffer b2 b1; // 触发深拷贝b1、b2各自拥有独立堆内存 return 0; }运行输出构造分配堆内存 深拷贝构造新建独立堆内存 析构释放堆内存 析构释放堆内存无崩溃两块独立堆内存各自析构释放。补充对比示例区分拷贝构造与拷贝赋值#include iostream #include string using namespace std; class Person { private: string m_name; public: Person(string name) : m_name(name) {} Person(const Person p) { m_name p.m_name; } ~Person() {} }; int main() { Person p1(张三); Person p2 p1; // 拷贝构造用p1初始化全新对象p2 Person p3(李四); p3 p1; // 拷贝赋值p3对象已经存在覆盖原有资源 return 0; }四、C三法则完整解读C三法则如果类需要自定义其中任意一个析构函数、拷贝构造函数、拷贝赋值运算符那么另外两个通常也需要手动定义。只手写析构释放内存不写拷贝构造浅拷贝造成双重释放崩溃手写析构拷贝构造不写拷贝赋值对象赋值操作依然会触发浅拷贝同样崩溃资源管理类三个函数配套手写保证对象复制安全五、工程编码规范与坑点总结✅ 推荐规范类内持有裸指针、管理堆内存资源必须手写深拷贝拷贝构造拷贝构造参数必须加const 禁止值传递值传递会无限递归调用拷贝构造深拷贝时先分配内存再拷贝数据保证内存有效资源类遵循C三法则配套实现析构、拷贝构造、拷贝赋值❌ 坑点与禁忌依赖编译器默认浅拷贝处理带堆内存的类双重释放崩溃拷贝构造参数写成值传递触发无限递归编译报错深拷贝忘记复制数据只复制指针大小混淆拷贝构造和拷贝赋值对象初始化触发拷贝构造已存在对象之间触发拷贝赋值六、本节思考题默认拷贝构造函数执行浅拷贝浅拷贝的本质是什么浅拷贝为什么会出现双重释放程序崩溃深拷贝核心思路是什么深拷贝和浅拷贝最根本区别简述C三法则是什么下期预告拷贝构造函数浅拷贝问题与工程解决方案