C 箴言:使用对象管理资源

2008-02-23 05:40:58来源:互联网 阅读 ()

新老客户大回馈,云服务器低至5折

  假设我们和一个投资(例如,股票,债券等)模型库一起工作,各种各样的投资形式从一个根类 Investment 派生出来:

class Investment { ... }; // root class of hierarchy of
// investment types

  进一步假设这个库使用了通过一个 factory 函数为我们提供特定 Investment 对象的方法:

Investment* createInvestment(); // return ptr to dynamically allocated
// object in the Investment hierarchy;
// the caller must delete it
// (parameters omitted for simplicity)

  通过注释指出,当 createInvestment 函数返回的对象不再使用时,由 createInvestment 的调用者负责删除他。那么,请考虑,写一个函数 f 来履行以下职责:

void f()
{
Investment *pInv = createInvestment(); // call factory function
... // use pInv
delete pInv; // release object
}

  这个看上去没问题,但是有几种情形会造成 f 在删除他从 createInvestment 得到的 investment 对象时失败。有可能在这个函数的 "..." 部分的某处有一个提前出现的 return 语句。假如这样一个 return 执行了,控制流程就再也无法到达 delete 语句。还可能发生的一个类似情况是假如 createInvestment 的使用和删除在一个循环里,而这个循环以一个 continue 或 goto 语句提前退出。更有,"..." 中的一些语句可能抛出一个异常。假如这样,控制流程不会再到达那个 delete。无论那个 delete 被如何跳过,我们泄漏的不但仅是容纳 investment 对象的内存,还包括那个对象持有的任何资源。

  当然,小心谨慎地编程能防止这各种错误,但考虑到这些代码可能会随着时间的流逝而发生变化。为了对软件进行维护,一些人可能会在没有完全把握对这个函数的资源管理策略的其他部分的影响的情况下增加一个 return 或 continue 语句。尤有甚者,f 的 "..." 部分可能调用了一个从不惯于抛出异常的函数,但是在他被“改良”后突然这样做了。依赖于 f 总能到达他的 delete 语句根本靠不住。

  为了确保 createInvestment 返回的资源总能被释放,我们需要将那些资源放入一个类中,这个类的析构函数在控制流程离开 f 的时候会自动释放资源。实际上,这只是本文介绍的观念的一半:将资源放到一个对象的内部,我们能够依赖 C 的自动地调用析构函数来确保资源被释放。(过一会儿我们还要介绍本文观念的另一半。)

  许多资源都是动态分配到堆上的,并在一个单独的块或函数内使用,而且应该在控制流程离开那个块或函数的时候释放。标准库的 auto_ptr 正是为这种情形量体裁衣的。auto_ptr 是个类似指针的对象(一个智能指针),他的析构函数自动在他指向的东西上调用 delete。下面就是如何使用 auto_ptr 来预防 f 的潜在的资源泄漏:

void f()
{
std::auto_ptr<Investment> pInv(createInvestment()); // call factory
// function
... // use pInv as
// before
} // automatically
// delete pInv via
// auto_ptr’s dtor

  这个简单的例子示范了使用对象管理资源的两个重要的方面:

  获得资源后应该立即移交给资源管理对象。如上,createInvestment 返回的资源被用来初始化即将用来管理他的 auto_ptr。实际上,因为获取一个资源并在同一个语句中初始化资源管理对象是如此常见,所以使用对象管理资源的观念也常常被称为 Resource Acquisition Is Initialization (RAII)。有时被获取的资源是被赋值给资源管理对象的,而不是初始化他们,但这两种方法都是在获取资源的同时就立即将他移交给资源管理对象。

  资源管理对象使用他们的析构函数确保资源被释放。因为当一个对象被销毁时(例如,当一个对象离开其活动范围)会自动调用析构函数,无论控制流程是怎样离开一个块的,资源都会被正确释放。假如释放资源的动作会引起异常抛出,事情就会变得棘手,但是,关于那些问题以后我将专题讲解,所以不必担心他。

  因为当一个 auto_ptr 被销毁的时候,会自动删除他所指向的东西,所以不要让超过一个的 auto_ptr 指向同一个对象很重要。假如发生了这种事情,那个对象就会被删除超过一次,而且会让您的程式通过捷径进入未定义行为。为了防止这个问题,auto_ptrs 具备不同寻常的特性:拷贝他们(通过拷贝构造函数或拷贝赋值运算符)就是将他们置为空,拷贝的指针被设想为资源的唯一任何权。

std::auto_ptr<Investment> // pInv1 points to the
pInv1(createInvestment()); // object returned from
// createInvestment

std::auto_ptr<Investment> pInv2(pInv1); // pInv2 now points to the
// object; pInv1 is now null

pInv1 = pInv2; // now pInv1 points to the
// object, and pInv2 is null

  这个奇怪的拷贝行为,增加了潜在的需求,就是通过 auto_ptrs 管理的资源必须绝对没有超过一个 auto_ptr 指向他们,这也就意味着 auto_ptrs 不是管理任何动态分配资源的最好方法。例如,STL 容器需要其内含物能表现出“正常的”拷贝行为,所以 auto_ptrs 的容器是不被允许的。

  相对于 auto_ptrs,另一个可选方案是个引用计数智能指针(reference-counting smart pointer, RCSP)。一个 RCSP 是个智能指针,他能持续跟踪有多少对象指向一个特定的资源,并能够在不再有任何东西指向那个资源的时候删除他。就这一点而论,RCSP 提供的行为类似于垃圾收集(garbage collection)。和垃圾收集不同的是,无论如何,RCSP 不能打破循环引用(例如,两个没有其他使用者的对象互相指向对方)。

  TR1 的 tr1::shared_ptr是个 RCSP,所以您能够这样写 f:

void f()
{
...

std::tr1::shared_ptr<Investment>
pInv(createInvestment()); // call factory function

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇: C 是一种糟糕的语言 Linux之父炮轰C

下一篇: 用C设计 用C 编码