news 2026/10/21 2:29:42

【C++】 智能指针RAII

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C++】 智能指针RAII

一.什么是智能指针?

智能指针是根据RAII的设计思路实现的,RAII(Resource Acquisition Is Initialization)资源获取即初始化,智能指针是由类实现的,是一种用于自动管理内存空间资源的工具,它能在程序结束时自动释放那些未释放的资源,从而帮助我们管理指针,避免内存泄漏,这里的资源可以是内存、文件指针、网络连接、互斥锁等等

智能指针类除了满足RAII的设计思路,还要方便资源的访问,所以智能指针类还会想迭代器类一样,重载 operator*/operator->/operator[] 等运算符,方便访问资源

二.智能指针的使用

程序内存泄漏的原因是千奇百怪的,不是我们想预防就避免得了的,比如下面这段程序

struct A { A(int a) :_a(a) { } ~A() { cout << "delete" << endl;// 直观是否调用析构 } int _a; }; void func(int a, int b) { A* a1 = new A(a); A* a2 = new A(b); try { if (a == 10) { string s("error"); throw s; } cout << a + b << endl; } catch(const int& a) { cout << a << endl; } delete a1; delete a2; } int main() { try { func(10, 20); } catch (const string& s) { cout << s << endl; } return 0; }

我们运行这段代码就会发现,类A的析构函数并没有执行

当然,实践中的情况会更加复杂,这时候要想避免内存泄漏,我们就可以利用智能指针来解决

struct A { A(int a) :_a(a) { } ~A() { cout << "delete" << endl; } int _a; }; void func(int a, int b) { shared_ptr<A> a1(new A(a)); shared_ptr<A> a2(new A(b)); try { if (a == 10) { string s("error"); throw s; } cout << a + b << endl; } catch(const int& a) { cout << a << endl; } } int main() { try { func(10, 20); } catch (const string& s) { cout << s << endl; } return 0; }

可以看到在func函数栈帧销毁之前,a1, a2已经被析构了,这是因为在栈帧销毁前会将函数中的变量全部销毁,而智能指针的本质也是一个类,所以这样就能够避免内存泄漏

三.常见的智能指针

指针类型版本优点缺点应用场景
auto_ptrC++98发行版本早一块内存空间只有一个auto_ptr管理,极易导致越界访问问题,一般被禁止使用一般不使用
unique_ptrC++11与auto_ptr相比,不支持拷贝构造/赋值,支持移动构造/赋值一块内存空间同样只有一个unique_ptr管理一块内存空间只需一个智能指针管理
shared_ptrC++11用引用计数支持多个shared_ptr一起管理一块内存空间在特定情况会出现循环引用问题,导致内存泄漏大多应用场景都适用
weak_ptrC++11辅助shared_ptr,处理循环引用问题只能用来辅助shared_ptr避免shared_ptr出现循环引用问题

在常见的智能指针类型中,auto_ptr只需略作了解即可,由于槽点太多在实际中一般被禁止使用;

其中shared_ptr需要我们详细了解,在面试中shared_ptr也是面试官常问的问题,后面我们就来深入了解并模拟实现unique_ptr,shared_ptr,并用weak_ptr来处理循环引用问题

四.unique_ptr的使用并模拟实现

1.构造函数

int main() { unique_ptr<int> up1(new int(10)); // unique_ptr<int> up2 = new int(20));// error return 0; }

可以看到unique_ptr的构造函数只支持显式化传参,不支持隐式类型转化,所以我们要用explicit修饰构造函数

2.拷贝构造/赋值

int main() { unique_ptr<int> up1(new int(10)); // unique_ptr<int> up2(up1);// error unique_ptr<int> up3; // up3 = up1;//error return 0; }

首先我们可以看到拷贝构造/赋值都是不可调用的(详细原因在实现shared_ptr时说明),所以我们的思路也是不能实现拷贝构造/赋值

3.移动拷贝/赋值

int main() { unique_ptr<int> up1(new int(10)); //unique_ptr<int> up2(move(up1)); unique_ptr<int> up2; up2 = move(up1); cout << *up2 << endl; return 0; }

可以看到unique_ptr是支持移动构造/赋值的,并且交换后的up1指向的空间已释放

4.运算符重载

int main() { unique_ptr<pair<string, int>> up1(new pair<string, int>("C++", 250)); unique_ptr<int> up2(new int(10)); cout << up1->first << ":" << up1->second << endl; cout << *up2 << endl; if (up1) cout << "not nullptr" << endl; return 0; }

从上面测试中可以发现,我们要重载的运算符至少要有operator->, operator*, operator bool

5.模拟代码

template<class T> class my_unique_ptr { public: explicit my_unique_ptr(T* ptr = nullptr) :_ptr(ptr) { } my_unique_ptr(const my_unique_ptr& up) = delete; my_unique_ptr& operator=(const my_unique_ptr& up) = delete; my_unique_ptr(my_unique_ptr&& up) :_ptr(up._ptr) { up._ptr = nullptr; } my_unique_ptr& operator=(my_unique_ptr&& up) { if (up._ptr != _ptr) { delete _ptr; _ptr = up._ptr; up._ptr = nullptr; } return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } operator bool() { return _ptr; } ~my_unique_ptr() { cout << "delete " << endl; delete _ptr; _ptr = nullptr; } private: T* _ptr; }; int main() { my_unique_ptr<pair<string, int>> up1(new pair<string, int>("C++", 250)); my_unique_ptr<int> up2(new int(10)); *up2 = 20; //my_unique_ptr<int> up3 = new int(20);// 不支持隐式类型转化 //my_unique_ptr<int> up4(up2);// 不支持拷贝构造 //my_unique_ptr<int> up5; //up5 = up1;// 不支持拷贝赋值 //my_unique_ptr<int> up6(new int(10)); //my_unique_ptr<int> up7(move(up6));// 移动构造 //my_unique_ptr<int> up8; //up8 = move(up6);// 移动赋值 cout << *up2 << endl;// operator* cout << up1->first << ":" << up1->second << endl;// operator-> if (up1)// operator bool cout << "not nullptr" << endl; return 0; }

五.智能指针的析构函数

我们都知道指针有很多用途,当我们不使用这个指针时,处理方法也有很多种:

但每个类只能有一个析构函数,那这时候我们可以定义一个function<void(type_name*)>类型的成员变量_del,再利用仿函数来初始化

那么下一篇就带来shared_ptr的详细实现过程

综上就是本期博客的所有内容,感谢阅读,也欢迎大家讨论纠错

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 2:28:45

UWB定位中继信标三合一锚点,如何降低部署成本与运维复杂度?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/21 2:27:34

《驾驭你的AI同事:WorkBuddy深度精通》045:4.3 技能的发布与版本管理

《驾驭你的AI同事:WorkBuddy深度精通》045:4.3 技能的发布与版本管理 本文是《唤醒你的 AI 同事:WorkBuddy 全栈指南》系列 卷二(深度精通) 的第 45 篇。前面讲了 Skill 的结构、脚本与引用组织、本地调试。这一篇解决"写完之后"的问题:怎么把它发出去、怎么管…

作者头像 李华
网站建设 2026/10/21 2:25:46

PB03蓝牙5.2二次开发实战:AT指令与SDK接口避坑指南

简介&#xff1a;这份资料面向蓝牙5.2二次开发的学习者与工程师&#xff0c;围绕安信可PB-03模块展开&#xff0c;该模块基于PHY6252低功耗蓝牙SoC&#xff0c;集成64KB SRAM、256KB Flash与96KB ROM&#xff0c;支持125Kbps至2Mbps多种速率及UART、GPIO、ADC、PWM、I2C、SPI、…

作者头像 李华
网站建设 2026/10/21 2:25:31

容器虚拟化基础--NameSpace空间隔离详解

目录 1. dd 命令详解 2. mkfs 命令详解 3. df 命令详解 4. mount 命令详解 5. unshare 命令详解 6. 实战操作一&#xff08;PID隔离&#xff09; 7. 实战操作二&#xff08;Mount隔离&#xff09; 望读者能通过此文了解隔离能力并不是 docker 提供的&#xff0c;而是操作…

作者头像 李华