您的位置:首页 > 编程语言 > C语言/C++

读书笔记《Effective C++》条款13:以对象管理资源

2017-05-07 12:29 411 查看
先看例子:

void f()
{
Investment* pInv = createInvestment();//返回指针,指向Investment继承体系内的动态分配对象。调用者有责任删除它。
//...
delete pInv;//释放pInv所指对象
}
这个看起来妥当,有delete释放资源。但有些情况下f函数可能无法删除它,可能因为“...”区域内的一个过早的return语句。如果这样一个return被执行起来,控制流就绝不会触及delete语句。类似情况发生在对createInvestment的使用及delete动作位于某循环内,而该循环由于某个continue或goto语句过早退出。最后一种可能是“...”区域内的语句抛出异常,果真如此控制流将再次不会执行delete语句。无论delete如何被略过去,我们泄露的不只是内含投资对象的那块内存,还包括那些投资对象所保存的任何资源。

当然,谨慎地编写程序可以防止这一类错误。但必须想想,代码可能会在时间渐渐过去后被修改。因此单纯依赖“f总是会执行其delete语句”是行不通的。

为确保createInvestment返回的资源总是被释放,我们需要将资源放进对象内,当控制流离开f,该对象的析构函数会自动释放那些资源。也就是说:把资源放进对象内,我们便可依赖C++的“析构函数自动调用机制”确保资源被释放。

标准库提供auto_ptr(模板auto_ptr是C++ 98提供的解决方案,C++ 11已将其摒弃, C++ 11新加智能指针:unique_ptr, shared_ptr以及weak_ptr)正是针对这种形式设计的特制产品。auto_ptr是个“类指针(pointer-like)对象”,也就是所谓的“智能指针”,其析构函数自动对其所指对象调用delete。

下面示范如何使用auto_ptr以避免f函数潜在的资源泄露可能性:

void f()
{
std::auto_ptr<Investment> pInv(createInvestment);
//...
}//经由auto_ptr的析构函数自动删除pInv
这个简单的例子示范“以对象管理资源”的两个关键想法:

1.获得资源后立刻放进管理对象内。以上代码中createInvestment返回的资源被当做其管理者auto_ptr的初值,实际上“以对象管理资源”的观念常被称为“资源取得时机便是初始化时机”(Resource Acquisition Is Initialization;RAII),因为我们几乎总是在获得一笔资源后于同一语句内以它初始化某个管理对象。有时候获得的资源被拿来赋值(而非初始化)某个管理对象,但不论哪一种做法,每一笔资源都在获得的同时立刻被放进管理对象中。

2.管理对象运用析构函数确保资源被释放。不论控制流如何离开区块,一旦对象被销毁(例如当对象离开作用域)其析构函数自然会被自动调用,于是资源被释放。如果资源释放动作可能导致抛出异常,事情变得有点棘手,但条款8已经能够解决这个问题。

由于auto_ptr被销毁时会自动删除它所指之物,所以一定要注意别让多个auto_ptr同时指向同一对象。如果这样,对象会被删除一次以上,那而会使你的程序出现未定义行为。为了预防这个问题,auto_ptr有一个不寻常的性质:若通过copy构造函数或copy assignment操作符复制它们,它们会变成null,而复制所得的指针将取得资源的唯一拥有权!

std::auto_ptr<int> p(new int(8));
std::auto_ptr<int> q;
q = p;//将所有权从p转给q,使q指向p指向的对象,并使p成为未绑定的auto_ptr对象
std::cout << *q;//执行时正确输出8
std::cout << *p;//执行时出现异常
auto_ptr只能用于管理从new返回的一个对象,它不能管理动态分配的数组。不能将auto_ptr存储在标准库容器类型中。

模板auto_ptr是C++ 98提供的解决方案,C++ 11已将其摒弃, C++ 11新加智能指针:unique_ptr, shared_ptr以及weak_ptr。

为什么摒弃auto_ptr?

从上述例子中看出,q = p语句后,p已经是被设为NULL了,所以下面的std::cout << *p 语句执行时出现程序崩溃。但如果这里把auto_ptr换成shared_ptr或unique_ptr后,程序就不会崩溃了。原因如下:

1.使用shared_ptr时运行正常,因为shared_ptr采用引用计数,p和q都指向同一块内存,在释放空间时因为事先要判断引用计数值的大小因此不会出现多次删除一个对象的错误。

2.使用unique_ptr时编译出错,与auto_ptr一样,unique_ptr也采用所有权模型,但在使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器因下述代码行出现错误:

std::unique_ptr<int> p(new int(8));
std::unique_ptr<int> q;
q = p;//编译器报错
这就是为何要摒弃auto_ptr的原因,一句话总结就是:避免潜在的内存崩溃问题。

编译器认为语句q = p非法,避免了p不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。

auto_ptr和shared_ptr两者都在析构函数内做delete而不是delete[]动作。那意味在动态分配而得的array身上使用auto_ptr或shared_ptr是个馊主意。尽管如此,但编译器仍能编译通过。

std::auto_ptr<std::string> aps(new std::string[10]);//馊主意!会用上错误的delete形式
std::shared_ptr<int> spi(new int[1024]);//相同问题
并没有特别针对“C++动态分配数组”而设计的类似auto_ptr或shared_ptr那样的东西。那是因为vector和string几乎总是可以取代动态分配而得的数组。当然,boost::scoped_array和boost::shared_array可以提供这样的行为。

要点:

1.为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。

2.两个常被使用的RAII class:unique_ptr和shared_ptr。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: