当前位置: 首页 > news >正文

【C++ 从基础到项目实战】C++(六):拷贝控制——浅拷贝与深拷贝,兼谈智能指针

📌 阅读时长:22分钟 | 关键词:C++、拷贝构造函数、浅拷贝、深拷贝、赋值运算符、智能指针、unique_ptr、shared_ptr

引言

上一篇文章我们学会了创建类、构造对象。但有一个容易踩坑的核心问题被有意跳过了:当一个对象被赋值给另一个,或者作为参数传递时,到底发生了什么?如果你的类里有指针成员,浅拷贝会给你带来毁灭性的行为——两个对象共用一个资源,一个销毁了,另一个就变成悬挂指针。这篇文章就来把这个坑填平。

一、默认拷贝:自带的"浅拷贝"陷阱

编译器会自动生成一个拷贝构造函数,但它只做浅拷贝——逐字节复制,指针复制的是地址,不是地址指向的内容:

classMyClass{public:int*data;MyClass(intvalue){data=newint(value);}// 编译器自动生成类似这样的拷贝构造函数:// MyClass(const MyClass &other) : data(other.data) {} ← 浅拷贝!};intmain(){MyClassobj1(10);MyClass obj2=obj1;// 浅拷贝:obj2.data 和 obj1.data 指向同一块内存std::cout<<*obj1.data<<std::endl;// 10std::cout<<*obj2.data<<std::endl;// 10// 💀 问题:两个对象指向同一块内存,析构时会 double delete!}

浅拷贝的三大危害

问题原因后果
双重释放两个指针指向同一块内存,析构时各自 delete 一次程序崩溃
数据互相干扰通过一个对象修改值,另一个"也变了"逻辑错误
悬挂指针一个对象先销毁释放了内存,另一个还在用未定义行为

二、深拷贝:自己动手,丰衣足食

深拷贝= 不仅拷贝指针本身,还要新分配一块内存,把内容一起拷过去:

classMyClass{public:int*data;MyClass(intvalue){data=newint(value);}// 自定义深拷贝构造函数MyClass(constMyClass&other){data=newint(*other.data);// 新分配内存,拷贝值}~MyClass(){deletedata;}};intmain(){MyClassobj1(10);MyClass obj2=obj1;// 深拷贝:各自拥有独立的内存*obj1.data=20;std::cout<<*obj2.data<<std::endl;// 仍然 = 10,互不影响 ✅}

浅拷贝 vs 深拷贝图解

浅拷贝: obj1.data ──→ [内存块: 10] ←── obj2.data (两个指针指向同一块) 深拷贝: obj1.data ──→ [内存块: 10] obj2.data ──→ [内存块: 10] (各自独立)

三、拷贝赋值运算符

除了拷贝构造(obj2 = obj1在声明时),还有一种情况是赋值已有对象:

classMyClass{public:int*data;MyClass(intvalue){data=newint(value);}// 深拷贝构造函数MyClass(constMyClass&other){data=newint(*other.data);}// 深拷贝赋值运算符MyClass&operator=(constMyClass&other){if(this!=&other){// ⚠️ 防止自赋值deletedata;// 先释放已有资源data=newint(*other.data);// 再分配新资源并拷贝}return*this;}~MyClass(){deletedata;}};intmain(){MyClassobj1(10),obj2(20);obj2=obj1;// 调用赋值运算符(深拷贝)}

四、三五法则(Rule of Three)

如果一个类需要自定义析构函数,那么几乎一定也需要自定义拷贝构造函数拷贝赋值运算符

Rule of Three(C++98): 析构函数 + 拷贝构造函数 + 拷贝赋值运算符 ↓ Rule of Five(C++11): 再 + 移动构造函数 + 移动赋值运算符
// 完整的三件套classDataArray{private:int*arr;intsize;public:DataArray(ints):size(s),arr(newint[s]){}DataArray(constDataArray&o):size(o.size),arr(newint[o.size]){// 拷贝构造std::copy(o.arr,o.arr+size,arr);}DataArray&operator=(constDataArray&o){// 拷贝赋值if(this!=&o){delete[]arr;size=o.size;arr=newint[size];std::copy(o.arr,o.arr+size,arr);}return*this;}~DataArray(){delete[]arr;}// 析构};

五、智能指针:告别手动 delete

C++11 引入智能指针,自动管理内存,从根本上避免浅拷贝/忘记 delete 的坑。

5.1 unique_ptr:独占所有权

unique_ptr不可复制,只能移动,确保只有一个指针拥有对象:

#include<memory>intmain(){autop1=std::make_unique<int>(10);// C++14 推荐写法// std::unique_ptr<int> p2 = p1; // ❌ 不可复制!autop2=std::move(p1);// ✅ 所有权转移// p1 现在为空if(p1)std::cout<<*p1;// 不会执行elsestd::cout<<"p1 已空"<<std::endl;std::cout<<*p2<<std::endl;// 10// p2 离开作用域自动 delete}

5.2 shared_ptr:共享所有权 + 引用计数

多个shared_ptr可共享同一块内存,最后一个释放时才 delete:

#include<memory>intmain(){autop1=std::make_shared<int>(10);{autop2=p1;// 引用计数 1→2std::cout<<*p2<<std::endl;// 10}// p2 离开,引用计数 2→1std::cout<<*p1<<std::endl;// 10(内存还在)}// p1 离开,引用计数 1→0,自动 delete

5.3 裸指针 vs 智能指针

特性裸指针T*unique_ptrshared_ptr
所有权无约束独占共享
复制❌(只能移动)✅(引用计数+1)
自动释放❌ 需手动 delete
循环引用⚠️ 需 weak_ptr 解决
性能开销极小引用计数有额外开销

💡 日常开发原则:能用 unique_ptr 就别用 shared_ptr,能用智能指针就别用裸指针

小结

序号知识点一句话总结
1浅拷贝只拷地址不拷内容,两个对象共用内存→双重释放
2深拷贝新分配内存+拷贝内容,各自独立
3拷贝赋值operator=实现深赋值,注意自赋值检查
4三五法则自定义析构时,记得也自定义拷贝构造和赋值
5unique_ptr独占所有权,不可复制,移动转移
6shared_ptr共享所有权,引用计数为0时自动释放

下一篇文章,我们将进入面向对象最强大的特性——继承与多态:如何复用代码、如何用虚函数实现"同一个接口,不同的行为"。


本文是「C++ 从基础到项目实战」系列的第 6 篇。关注我,不错过后续更新。

http://www.jsqmd.com/news/940908/

相关文章:

  • Jetson Orin Nano 部署 PaddleOCR C++ 全流程实战指南
  • 别再当‘黑盒’玩家了!用GradCAM给YOLOv8做个‘X光’,看看它到底‘看’到了什么
  • 教育工作者AI工具应用速成课(限200所试点校内部资料首次公开)
  • Claude敏感性分析实战手册(企业级合规红线预警系统首次披露)
  • Tool-Graphify
  • 别再为地图国界线发愁了!用Cartopy+cnmaps绘制专业气象图(附正确国界SHP文件获取指南)
  • GitHub 中文化插件:5分钟打造你的中文GitHub体验
  • 无细胞蛋白表达(CFPS)技术详解:AI蛋白设计、膜蛋白表达与难表达蛋白制备新方案
  • 非公度线缺陷下蜂巢晶格狄拉克点边缘态的多尺度分析
  • 今天不整合,明天就掉队:2024Q2起,超61%的数据分析师岗位要求“AI-Augmented Analytics”实战能力(LinkedIn人才趋势预警)
  • 国内主流人才测评系统实测对比:合规与效能双维度评测 - 得赢
  • Godot 4.2 2D游戏开发中那些‘学了就忘’的实用技巧合集:动画树、Shader、状态机与场景管理
  • 物联网系统架构设计:从连接融合、边缘智能到安全与数据价值
  • Video2X深度评测:如何用AI视频超分辨率技术让老视频重获新生?
  • 告别imgaug!用Roboflow给YOLOv8数据集做增强,保姆级图文教程
  • MATLAB一键运行的数字全息FFT重建实操资源(含实测全息图+光路图+可视化脚本)
  • 用LMV358M给工频信号做‘美容’:手把手设计五阶巴特沃斯滤波与直流偏置电路
  • 如何将B站视频转为文字:面向内容创作者的高效解决方案
  • AI工具API集成开发不是写curl!资深SRE总监亲述:如何用OpenTelemetry+Prometheus+Jaeger实现毫秒级故障定位(含Grafana看板一键导入)
  • HBuilderX中可直接运行的蓝牙通信实战包:含状态检测、收发控制、安卓原生对照与JDY-08/MLT-BT05模块调试支持
  • 告别Photon?用Mirror给Unity多人游戏做网络同步的保姆级配置流程
  • 别再只盯着UNet了!盘点2024年图像去模糊的5个新思路(附代码链接)
  • Sora 2赋能城市传播:从脚本生成到成片交付,92%市级宣传部门未公开的7类合规性审查清单(含广电总局最新备案模板)
  • VS 2022 免费激活永久密钥
  • CodeXGLUE:代码智能领域的基准测试平台与实战指南
  • 冷知识!你的论文查重其实可以不花钱?书匠策AI这个隐藏功能太香了
  • 问答与提问生成联合模型:T5实现与多任务学习调优
  • LangChain异步调用实战:批量处理100条文本,速度提升3倍的保姆级配置指南
  • 评测全网10款主流降AIGC平台:帮你锁定达标神器
  • 2026年6月北京别墅装修公司推荐:五大排名专业评测价格适用场景 - 品牌推荐