Effective C++条款13:资源管理之(以对象管理资源)
原创
©著作权归作者所有:来自51CTO博客作者董哥的黑板报的原创作品,请联系作者获取转载授权,否则将追究法律责任
一、资源释放失败的案例
- 例如我们使用一个用来塑模投资行为(例如股票,债券等等)的程序库,其中各式各样的投资类型继承于一个基类Investment:
- 现在建立通过一个工厂函数供应我们获得某特定的Investment对象:
Investment* createInvestment();
- 如果我们在某作用域内调用这个函数返回的对象,那么使用完这个对象之后要复制删除这个对象
void f()
{
Investment* pInv = createInvestment();
delete pInv;
}
这种程序设计的缺陷
- 缺陷主要在于我们可能无法释放获取的pInv对象:
- 如果在delete之前有return语句导致函数执行结束,那么对象就无法释放
- 如果在delete之前程序抛出异常,那么也无法释放对象
- 如果这段代码在之后软件开发维护过程中被修改,那么后人可能无法知道要释放这个pInv对象,因为单纯的靠函数f中的delete语句来释放对象是行不通的
二、“以对象管理资源”的思想(RAII)
- 对对象进行管理最好的方法就是让其自动申请资源和释放资源,这个时候我们会联想到构造函数(自动执行)和析构函数(自动执行)
- 以对象管理资源的思想:
- 获得资源后立刻放进管理对象内:获得资源之后将其封装到类中,例如shared_ptr等智能指针。实际上“以对象管理资源”的观念常被称为“资源获得时机便是初始化时机”(Resource Acquisition Is Initialization,RAII)
- 管理对象运用析构函数确保资源被释放:当离开作用域之后,对象可以调用析构函数自动的释放资源,而无须我们手动释放。但是如果析构函数抛出异常,可能需要自己手动处理(析构函数异常处理可以参阅条款8)
- C++程序库提供了两种类,更加安全的管理自己的资源,分别是:
- auto_ptr:已弃用,但是本文还是要介绍一下。它已经被unique_ptr替代(详细使用可参阅:)
- shared_ptr:也是本文要介绍的。(详细使用可参阅:)
三、auto_ptr
- auto_ptr是个智能指针,其析构函数自动对其所指对象调用delete
- 例如我们修改函数f,并利用auto_ptr获得对象,在函数作用域结束之后,资源自动释放
void f()
{
std::auto_ptr<Investment> pInv(createInvestment());
}
- auto_ptr对象的唯一性:auto_ptr只保存自己管理对象的一份副本,因此当auto_ptr被赋值或复制时,就会将自己的资源管理权转交给它人,从而是自己变为null
std::auto_ptr<Investment> pInv1(createInvestment());
std::auto_ptr<Investment> pInv2(pInv1);
pInv1 = pInv2;
四、shared_ptr
- shared_ptr也是只能指针,但是其与autp_ptr不同,其是“引用计数型智能指针”(RCSP),也就是多个shared_ptr可以同时指向一个管理对象
- shared_ptr的详细介绍参阅
五、没有针对“动态分配数组”而设计的智能指针
- 注意:auto_ptr和shared_ptr析构函数中做释放资源使用的是delete而不是delete [],因此将动态数组绑定于智能指针对象上是不行的,但是数组是可以与智能指针使用的,详情可以参阅:
- 例如下面的操作都是错误的
std::auto_ptr<std::string> aps(new std::string[10]);
std::shared_ptr<int> aps(new int[1024]);
- C++没有为动态分配数组而设计的智能指针类,是因为C++已经有了vector和string这样的管理类,这些类已经够我们使用了,因此提供动态分配数组管理的类是多余的
- 但是Boost库中的boost::scoped_array和boost::shared_array类是接近于为数组而设计的类,因此你们想要了解的话,可以参阅这两个类
六、总结
- 为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源
- 两个常被使用的RAII类分别是tr1::shared_ptr和auto_ptr(以抛弃不再使用)
- shared_ptr:前者通常是较佳选择,因为其拷贝行为比较直观
- auto_ptr:复制动作会使它(被复制对象)指向null