当前位置: 首页 > 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.rkmt.cn/news/1452341.html

相关文章:

  • Jetson Orin Nano 部署 PaddleOCR C++ 全流程实战指南
  • 别再当‘黑盒’玩家了!用GradCAM给YOLOv8做个‘X光’,看看它到底‘看’到了什么
  • Tool-Graphify
  • 别再为地图国界线发愁了!用Cartopy+cnmaps绘制专业气象图(附正确国界SHP文件获取指南)
  • 非公度线缺陷下蜂巢晶格狄拉克点边缘态的多尺度分析
  • 今天不整合,明天就掉队:2024Q2起,超61%的数据分析师岗位要求“AI-Augmented Analytics”实战能力(LinkedIn人才趋势预警)
  • AI工具API集成开发不是写curl!资深SRE总监亲述:如何用OpenTelemetry+Prometheus+Jaeger实现毫秒级故障定位(含Grafana看板一键导入)
  • Sora 2赋能城市传播:从脚本生成到成片交付,92%市级宣传部门未公开的7类合规性审查清单(含广电总局最新备案模板)
  • 告别Photon?用Mirror给Unity多人游戏做网络同步的保姆级配置流程
  • HBuilderX中可直接运行的蓝牙通信实战包:含状态检测、收发控制、安卓原生对照与JDY-08/MLT-BT05模块调试支持
  • 问答与提问生成联合模型:T5实现与多任务学习调优
  • LangChain异步调用实战:批量处理100条文本,速度提升3倍的保姆级配置指南
  • 评测全网10款主流降AIGC平台:帮你锁定达标神器
  • UE5.3 + Rider 编译 GAS 插件踩坑实录:从 DirectX 报错到模块配置的完整修复流程
  • 2026年6月北京别墅装修公司推荐:五大排名专业评测价格适用场景 - 品牌推荐
  • 广告机项目实战:RK3588 Android13上搞定RTL8852BS WiFi蓝牙模块的完整踩坑记录
  • 微软研究院开放数据项目:云端数据即服务如何重塑AI研究与应用
  • 基于缺陷函数框架的黎曼ζ函数奇数点数值逼近方法
  • 终极免费音频编辑指南:Audacity完整使用教程与实用技巧
  • 从iPhone越狱到AI盒子:George Hotz的tinygrad框架,如何用几千行代码跑通Stable Diffusion?
  • 2026年6月最新视频转文字工具横评:格镜凭什么成为全网第一?
  • UE5 VR项目避坑指南:Interaction Component里的Select与Grab组件,别再乱配了!
  • 2026年6月抛丸机厂家推荐:TOP5排名专业评测重型装备清理案例价格 - 品牌推荐
  • Computex上我亲眼看到:程序员的“对手“已经不是人类了
  • 从‘删库跑路’到精准操作:手把手教你用jQuery的DOM方法(append, remove, empty)玩转动态网页
  • 2025-2026年国内十大企业管理咨询公司排行榜推荐:TOP10评测适用场景与注意事项特点 - 品牌推荐
  • Bresenham画圆算法在单片机ILI9806G屏幕上的移植指南:从公式推导到打点函数封装
  • 如何让微信在手机和平板同时登录?WeChatPad为你提供智能解决方案
  • 告别单设备束缚:WeChatPad开启微信双端同步新时代
  • 三步实现智能文献管理革命:Zotero-GPT完全指南