[翻译] Effective C++, 3rd Edition, Item 13: 使用 objects(对象)管理资源

Item 13: 使用 objects(对象)管理资源

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.csdn.net/fatalerror99/

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

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

进一步假设这个库为我们提供特定 Investment objects(对象)的方法是通过一个 factory function(工厂函数)(参见 Item 7)达成的:

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

就像注释指出的,当 createInvestment 函数返回的 object(对象)不再使用时,由 createInvestment 的调用者负责删除它。那么,考虑,写一个函数 f 来履行这个职责:

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

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

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

为了确保 createInvestment 返回的资源总能被释放,我们需要将那些资源放入一个 object(对象)中,这个 object(对象)的 destructor(析构函数)在控制流程离开 f 的时候会自动释放资源。实际上,这只是本 Item 背后的思想的一半:通过将资源放到 objects(对象)的内部,我们可以依赖 C++ 的 automatic destructor invocation(自动的析构函数调用)来确保资源被释放。(过一会儿我们要讨论本 Item 的思想的另一半。)

多数资源都是在堆上 dynamically allocated(动态分配)的,在一个单独的 block(块)或 function(函数)内使用,而且应该在控制流程离开那个 block(块)或 function(函数)的时候被释放。标准库的 auto_ptr 正是为这种情形量体裁衣的。auto_ptr 是一个 pointer-like object(类指针对象)(一个 smart pointer(智能指针)),它的 destructor(析构函数)自动在它指向的东西上调用 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

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

  • 获取资源后立即移交给 resource-managing objects(资源管理对象)。前面,createInvestment 返回的资源被用来初始化即将用来管理它的 auto_ptr。实际上,因为获取一个资源并在同一个语句中初始化一个 resource-managing object(资源管理对象)是如此常见,所以使用 objects(对象)管理资源的观念常常被称为 Resource Acquisition Is Initialization (RAII)。有时被获取的资源是被赋值给 resource-managing object(资源管理对象),而不是初始化它们的,但这两种方法都是在获取资源的同时就立即将它移交给一个 resource-managing object(资源管理对象)。
  • resource-managing objects(资源管理对象)使用它们的 destructors(析构函数)确保资源被释放。因为当一个 object(对象)被销毁时(例如,当一个 object(对象)离开其活动范围)会自动调用 destructors(析构函数),无论控制流程是怎样离开一个 block(块)的,资源都会被正确释放。如果释放资源的动作会导致 exceptions(异常)被抛出,事情就会变得棘手,但是那是 Item 8 讲述的内容,所以在这里我们不必担心它。

因为当一个 auto_ptr 被销毁的时候,会自动删除它所指向的东西,所以不要让超过一个的 auto_ptr 指向一个 object(对象)非常重要。如果发生了这种事情,那个 object(对象)就会被删除超过一次,而且会让你的程序通过捷径进入 undefined behavior(未定义行为)。为了防止这样的问题,auto_ptrs 具有不同寻常的特性:拷贝它们(通过 copy constructor(拷贝构造函数)或者 copy assignment operator(拷贝赋值运算符))就是将它们置为空,而拷贝的指针取得资源的唯一所有权。

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 不是管理所有 dynamically allocated resources(动态分配资源)的最好方法。例如,STL containers(容器)要求其内含物能表现出“正常的”拷贝行为,所以 auto_ptrs 的容器是不被允许的。

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

TR1 的 tr1::shared_ptr(参见 Item 54)是一个 RCSP,所以你可以这样写 f

void f()
{
  ...

  std::tr1::shared_ptr<Investment>
    pInv(createInvestment());             // call factory function
  ...                                     // use pInv as before
}                                         // automatically delete
                                          // pInv via shared_ptr's dtor

这里的代码看上去和使用 auto_ptr 的几乎相同,但是拷贝 shared_ptrs 的行为却自然得多:

void f()
{
  ...

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

  std::tr1::shared_ptr<Investment>          // both
pInv1 and pInv2 now
    pInv2(pInv1);                           // point to the object

  pInv1 = pInv2;                            // ditto — nothing has
                                            // changed
  ...
}                                           // pInv1 and pInv2 are
                                            // destroyed, and the
                                            // object they point to is
                                            // automatically deleted

因为拷贝 tr1::shared_ptrs 的工作“符合预期”,它们能被用于 STL containers(容器)以及其它和 auto_ptr 的非正统的拷贝行为不相容的环境中。

可是,不要误解,本 Item 不是关于 auto_ptrtr1::shared_ptr 或任何其它种类的 smart pointer(智能指针)的。而是关于使用 objects(对象)管理资源的重要性的。auto_ptrtr1::shared_ptr 仅仅是做这些事的 objects(对象)的例子。(关于 tr1::shared_ptr 的更多信息,请参考 Item 1418 和 54。)

auto_ptrtr1::shared_ptr 都在它们的 destructors(析构函数)中使用 delete,而不是 delete []。(Item 16 描述两者的差异。)这就意味着将 auto_ptrtr1::shared_ptr 用于 dynamically allocated arrays(动态分配数组)是个馊主意,可是,很遗憾,那居然可以编译:

std::auto_ptr<std::string>                       // bad idea! the wrong
  aps(new std::string[10]);                      // delete form will be used

std::tr1::shared_ptr<int> spi(new int[1024]);    // same problem

你可能会吃惊地发现 C++ 中没有可用于 dynamically allocated arrays(动态分配数组)的类似 auto_ptrtr1::shared_ptr 这样的东西,甚至在 TR1 中也没有。那是因为 vectorstring 几乎总是能代替 dynamically allocated arrays(动态分配数组)。如果你依然觉得有可用于数组的类似 auto_ptrtr1::shared_ptr 的 classes(类)更好一些的话,可以去看看 Boost(参见 Item 55)。在那里,你将如愿以偿地找到 boost::scoped_arrayboost::shared_array 两个 classes(类)提供你在寻找的行为。

本 Item 的关于使用 objects(对象)管理资源的指导间接表明:如果你手动释放资源(例如,使用 delete,而不是在一个 resource-managing class(资源管理类)中),你就是在自找麻烦。像 auto_ptrtr1::shared_ptr 这样的 pre-canned resource-managing classes(预制资源管理类)通常会使遵循本 Item 的建议变得容易,但有时,你使用了一个资源,而这些 pre-fab classes(预加工类)不能如你所愿地做事。如果碰上这种情况,你就需要精心打造你自己的 resource-managing classes(资源管理类)。那也并非困难得可怕,但它包含一些需要你细心考虑的微妙之处。那些需要考虑的事项是 Item 1415 的主题。

作为最后的意见,我必须指出 createInvestment 的 raw pointer(裸指针)的返回形式就是一份 resource leak(资源泄漏)的请帖,因为调用者忘记在他们取回来的指针上调用 delete 实在是太容易了。(即使他们使用一个 auto_ptrtr1::shared_ptr 来实行 delete,他们仍然必须记住将 createInvestment 的返回值存储到一个 smart pointer object(智能指针对象)中。)对付这个问题调用需要改变 createInvestment 的接口,这是我在 Item 18 中安排的主题。

Things to Remember

  • 为了防止 resource leaks(资源泄漏),使用 RAII objects(对象),在 RAII objects(对象)的 constructors(构造函数)中获取资源并在它们的 destructors(析构函数)中释放它们。
  • 两个通用的 RAII classes(类)是 tr1::shared_ptrauto_ptrtr1::shared_ptr 通常是更好的选择,因为它的拷贝时的行为是符合直觉的。拷贝一个 auto_ptr 是将它置为空。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值