大学IT网 - 最懂大学生的IT学习网站! QQ资料交流群:367606806
当前位置:大学IT网 > C++技巧 > C++使用对象管理资源

C++使用对象管理资源

关键词:C++对象管理资源  阅读(497) 赞(13)

[摘要]本文主要是对C++技巧:使用对象管理资源的讲解,希望对您学习C++有所帮助!

  假定我们和一个投资(例如,股票,债券等)模型库一同义务,各种各样的投资方式从一个根类 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
... // 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 容器以及其它和 auto_ptr 的非正统的拷贝行为不相容的环境中。

  不要搞错,本文不是关于 about auto_ptr,tr1::shared_ptr 或任何其它种类的智能指针。而是关于运用对象管理资源的重要性的。about auto_ptr 和 tr1::shared_ptr 仅仅是做这些事的对象的例子。(关于 tr1::shared_ptr 的更多信息,请参考 Item 14,18 和 54。)

  about auto_ptr 和 tr1::shared_ptr 都在它们的析构函数中运用 delete,而不是 delete []。这就意味着将 about auto_ptr 或 tr1::shared_ptr 用于静态分配的数组是个馊主意,可是,可悲的是,那居然可以编译:

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++ 中没有可用于静态分配数组的类似 auto_ptr 或 tr1::shared_ptr 这样的东西,甚至在 TR1 中也没有。那是由于 vector 和 string 几乎总是能替代静态分配数组。假定你依然觉得有可用于数组的类似 auto_ptr 和类似 tr1::shared_ptr 的类更好一些的话,可以去看看 Boost。在那里,你将高兴地找到 boost::scoped_array 和 boost::shared_array 两个类提供你在寻觅的行为。 本 Item 的关于运用对象管理资源的指点直接标明:假定你手动释放资源(例如,运用 delete,而不运用资源管理类),你就是在自找费事。像 auto_ptr 和 tr1::shared_ptr 这样的预制的资源管理类通常会使本文的建议变得容易,但有时,你运用了一个资源,而这些预加工的类不能如你所愿地做事。假定碰上这种情况,你就需求精心打造你自己的资源管理类。那也并非困难得可怕,但它包括一些需求你细心思索的巧妙之处。

  作为最后的意见,我必需指出 createInvestment 的未加工指针的前往方式就是资源透露的请帖,由于调用者遗忘在他们取回来的指针上调用 delete 真实是太容易了。(即使他们运用一个 auto_ptr 或 tr1::shared_ptr 来完成 delete,他们仍然必需记住将 createInvestment 的前往值存储到智能指针对象中。)对付这个效果需求改动 createInvestment 的接口。

  Things to Remember

  ·为了防止资源透露,运用 RAII 对象,在 RAII 对象的构造函数中获得资源并在析构函数中释放它们。

  ·两个通用的 RAII 是 tr1::shared_ptr 和 auto_ptr。tr1::shared_ptr 通常是更好的选择,由于它的拷贝时的行为是契合直觉的。拷贝一个 auto_ptr 是将它置为空。



相关评论