天天看点

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

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

摘要

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

讨论

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

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

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

void fun( shared_ptr sp1, shared_ptr sp2 );

// ……

fun( shared_ptr(new widget), shared_ptr(new widget) );<code>`</code>`

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

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

继续阅读