《C++编程规范:101条规则、准则与最佳实践》——2.9 确保资源为对象所拥有。使用显式的RAII和智能指针-阿里云开发者社区

开发者社区> 异步社区> 正文

《C++编程规范:101条规则、准则与最佳实践》——2.9 确保资源为对象所拥有。使用显式的RAII和智能指针

简介:
+关注继续查看

本节书摘来自异步社区出版社《C++编程规范:101条规则、准则与最佳实践》一书中的第2章,第2.9节,作者:【加】Herb Sutter , 【罗】Andrei,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.9 确保资源为对象所拥有。使用显式的RAII和智能指针

摘要
利器在手,不要再徒手为之:C++的“资源获取即初始化”(Resource Acquisition Is Initialization,RAII)惯用法是正确处理资源的利器。RAII使编译器能够提供强大且自动的保证,这在其他语言中可是需要脆弱的手工编写的惯用法才能实现的。分配原始资源的时候,应该立即将其传递给属主对象。永远不要在一条语句中分配一个以上的资源。

讨论
C++语言所强制施行的构造函数/析构函数对称反映了资源获取/释放函数对比如fopen/fclose、lock/unlock和new/delete的本质的对称性。这使具有资源获取的构造函数和具有资源释放的析构函数的基于栈(或引用计数)的对象成为了自动化资源管理和清除的极佳工具。

这种自动化很容易实现、简洁、低成本而且天生防错。如果不予采用,就需要手工将调用正确配对,包括存在分支控制流和异常的情形,这可是很不容易而且需要注意力高度集中的任务。既然C++已经通过易用的RAII提供了如此直接的自动化,这种C语言式的仍然依赖于对资源解除分配的微观管理方式就是不可接受的了。

每当处理需要配对的获取/释放函数调用的资源时,都应该将资源封装在一个对象中,让对象为我们强制配对,并在其析构函数中执行资源释放。例如,我们无需直接调用一对非成员函数 OpenPort/ClosePort,而是可以考虑如下方法:

class Port {
public:
 Port( const string& destination ); // 调用OpenPort
 ~Port();                       // 调用ClosePort
 // ……通常无法复制端口,因此需要禁用复制和赋值……
};

void DoSomething() {
 Port port1( "server1:80" );
 // ……
}// 不会忘记关闭_port1_;它会在作用域结束时自动关闭

shared_ptr<Port> port2 = /*…*/;    // port2在最后一个引用它的
                               // shared_ptr离开作用域后关闭```
还可以使用实现了这种模式的软件库(参阅[Alexandrescu00c])。

在实现RAII时,要小心复制构造和赋值(见第49条),编译器生成的版本可能并不正确。如果复制没有意义,请通过将复制构造和赋值设为私有并且不做定义来明确禁用二者(见第53条)。否则,让复制构造函数复制资源或者引用计数所使用的次数,并让赋值操作符如法炮制,如果必要,同时还要确保它释放了最开始持有的资源。一个经典的疏漏是在新资源成功复制之前释放了老资源(见第71条)。

确保所有资源都为对象所有。最好用智能指针而不是原始指针来保存动态分配的资源。同样,应该在自己的语句中执行显式的资源分配(比如new),而且每次都应该马上将分配的资源赋予管理对象(比如shared_ptr),否则,就可能泄漏资源,因为函数参数的计算顺序是未定义的(见第31条)。例如:

void Fun( shared_ptr sp1, shared_ptr sp2 );
// ……
Fun( shared_ptr(new Widget), shared_ptr(new Widget) );``
这种代码是不安全的。C++标准给了编译器巨大的回旋余地,可以将构成函数两个参数的两个表达式重新排序。说得更具体一些,就是编译器可以交叉执行两个表达式:可能先执行两个对象的内存分配(通过调用operator new),然后再试图调用两个Widget构造函数。这恰恰为资源泄漏准备了温床,因为如果其中一个构造函数调用抛出异常的话,另一个对象的内存就永远也没有机会释放了!(详细情况请参阅 [Sutter02]。)

这种微妙的问题有一个简单的解决办法:遵循建议,绝对不要在一条语句中分配一个以上的资源,应该在自己的代码语句中执行显式的资源分配(比如new),而且每次都应该马上将分配的资源赋予管理对象(比如shared_ptr)。例如:

shared_ptr<Widget> sp1(new Widget), sp2(new Widget);
Fun( sp1, sp2 );```
另见第31条,了解使用这种风格的其他优点。

例外情况
智能指针有可能会被过度使用。如果被指向的对象只对有限的代码(比如纯粹在类的内部,诸如一个Tree类的内部节点导航指针)可见,那么原始指针就够用了。

参考文献
[Alexandrescu00c] ● [Cline99] §31.03-05 ● [Dewhurst03] §24, §67 ● [Meyers96] §9-10 ● [Milewski01] ● [Stroustrup00] §14.3-4, §25.7, §E.3, §E.6 ● [Sutter00] §16 ● [Sutter02] §20-21 ● [Vandevoorde03] §20.1.4

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Web前端优化常用规则
一,尽量减少HTTP请求 二,使用CDN(内容分发网络) 三,添加Expire/Cache-Control头 四,启用Gzip压缩 五,将CSS放在层叠样式表放到head里面 六,将Script放到页面最下面 七,在CSS中使用Expressions 八,把Js和CSS放到外部文件...
852 0
阿里云Cloud Shell中使用日志服务CLI最佳实践
目前阿里云云命令行Cloud Shell已经部署日志服务CLI,免部署配置,一键管理日志服务资源与下载日志服务数据更轻松!
11440 0
使用ADO对象添加、修改、删除数据
使用ADO对象对数据库中的数据进行添加、修改和删除等操作。首先创建一个ADO类,通过ADO类连接数据库,并打开记录集。例如,使用ADO对象添加、修改、删除数据,程序设计步骤如下:(1)创建一个基于对话框的应用程序,将对话框的Caption属性修改“使用ADO对象添加、修改、删除数据”。
797 0
【最佳实践】使用 Elasticsearch SQL 实现数据查询
如何使用 Elasticsearch SQL 来对我们的数据进行查询。
2344 0
Linux 中查看进程及资源使用情况
Linux 中查看进程及资源使用情况自带的 top 命令类似于平时我们使用的任务管理器,能够列出当前系统中的进程及资源的使用情况。 $ man top top - display Linux tasks 使用起来很简单,不加任何参数的情况下已经很实用了。
1998 0
PostgreSQL 持续稳定使用的小技巧 - 最佳实践、规约、规范
PostgreSQL 持续稳定使用的小技巧 - 最佳实践、规约、规范
3998 0
+关注
异步社区
异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。
12049
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载