C++的优秀特性6:智能指针
(转载请注明原创于潘多拉盒子)
智能指针(Smart Pointer)是C++非常重要的特性。考虑如下一段使用简单指针(Plain Pointer)的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | A* a = new A(); B* b = new B(); ...... if (condition1) { // return之前必须delete所有new出来的对象 delete a; delete b; return true ; } ...... if (condition2) { // return之前必须delete所有new出来的对象 delete a; delete b; return true ; } ....... if (condition3) { // throw 之前必须delete所有new出来的对象 delete a; delete b; throw std::exception( "bad condition3" ); } // 有异常必须catch,在rethrow之前delete所有new出来的对象 try { ...... } catch (...) // 三个点“...”用于此处捕捉所有异常 { delete a; delete b; throw ; // rethrow } |
这种方式非常繁琐,也很容易犯错误(error-prone)。有没有一种方法,能管理new出来的对象,并且在return/throw/异常发生的时候自动释放这些对象呢?按照《C++的优秀特性3:构造函数和析构函数》的介绍,有一种借助构造函数和析构函数实现的模版类,可以实现这样的功能。这就是智能指针。比如标准库STL中实现了一种智能指针std::auto_ptr。有了这种智能指针,前面的程序就简单了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | std::auto_ptr<A> a( new A()); std::auto_ptr<B> b( new B()); ......<br>a->foo(); // 像普通指针一样使用<br>(*b).bar(); // 像普通指针一样使用<br> if (condition1) { // return之前无需delete所有new出来的对象 return true ; } ...... if (condition2) { // return之前无需delete所有new出来的对象 return true ; } ....... if (condition3) { // throw 之前无需delete所有new出来的对象 throw std::exception( "bad condition3" ); } // 有异常不处理的时候也不用catch和rethrow了 |
一个智能指针指向一个对象叫做这个指针持有这个对象,也就是指针是这个对象的持有者。持有者负责在适当的时机释放持有的对象。常用的智能指针有3种:
- 单持有者,e.g. std::auto_ptr。单持有者保证每个new出来的对象只有一个持有者,单这种持有关系可以转移,转移之后原持有者不再持有对象。
- 局部持有者,e.g. boost::scoped_ptr。类似单持有者,单持有关系无法转移。这决定了这种持有关系不可能脱离当前的作用域(scope),因此得名。
- 共享持有者,e.g. boost::shared_ptr。一个对象可以被一个或多个这样的共享持有者持有,持有关系可以转移,转移之后原持有者继续持有该对象,直到原持有者被析构。当一个对象的最后一个持有者析构时,持有的对象被析构。
在实际中,由于#1(单持有者)的持有关系可以转移,常常是造成麻烦,而应用场景跟#2(局部持有者)又很接近,因此Google C++规范中禁止使用auto_ptr,而建议使用scoped_ptr。共享持有者是很常用的,基本上适用于大多数的简单指针应用的情况。但是,由于shared_ptr是为共享持有关系设计的,因此为了和scoped_ptr区分,在scoped_ptr适用的场景中,不要适用shared_ptr。
实际中常见的一种错误是,智能指针持有了不该持有的对象。比如:
1 2 | std::string name = "ZHANG San" ; std::auto_ptr<std::string> nameHolder(&name); // 持有了不是new出来的对象,会导致segmentation fault |
这其实是一种混用持有关系的问题。这可以简单的归纳为以下几种情况:
- 直接定义在栈上或data区的变量,不需要delete的。
- new出来的对象(在堆上),但程序员自己delete的。
- 不同类型的智能指针持有的对象。
这几种持有情况互相不能混用,否则会segmentation fault。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!