C++中的健壮指针和资源管理(2)

来源:互联网 发布:怎么定义输入数组java 编辑:程序博客网 时间:2024/05/16 14:08
 
Resource Transfer    到目前为止,我们所讨论的一直是生命周期在一个单独的作用域内的资源。现在我们要解决一个困难的问题--如何在不同的作用域间安全的传递资源。这一问题在当你处理容器的时候会变得十分明显。你可以动态的创建一串对象,将它们存放至一个容器中,然后将它们取出,并且在最终安排它们。为了能够让这安全的工作--没有泄露--对象需要改变其所有者。  这个问题的一个非常显而易见的解决方法是使用Smart Pointer,无论是在加入容器前还是还找到它们以后。这是他如何运作的,你加入Release方法到Smart Pointer中:template <class T>T * SPtr<T>::Release (){   T * pTmp = _p;   _p = 0;   return pTmp;}注意在Release调用以后,Smart Pointer就不再是对象的所有者了--它内部的指针指向空。现在,调用了Release都必须是一个负责的人并且迅速隐藏返回的指针到新的所有者对象中。在我们的例子中,容器调用了Release,比如这个Stack的例子:void Stack::Push (SPtr <Item> & item) throw (char *){   if (_top == maxStack)      throw "Stack overflow";   _arr [_top++] = item.Release ();};同样的,你也可以再你的代码中用加强Release的可靠性。  相应的Pop方法要做些什么呢?他应该释放了资源并祈祷调用它的是一个负责的人而且立即作一个资源传递它到一个Smart Pointer?这听起来并不好。Strong Pointers    资源管理在内容索引(Windows NT Server上的一部分,现在是Windows 2000)上工作,并且,我对这十分满意。然后我开始想……这一方法是在这样一个完整的系统中形成的,如果可以把它内建入语言的本身岂不是一件非常好?我提出了强指针(Strong Pointer)和弱指针(Weak Pointer)。一个Strong Pointer会在许多地方和我们这个SPtr相似--它在超出它的作用域后会清除他所指向的对象。资源传递会以强指针赋值的形式进行。也可以有Weak Pointer存在,它们用来访问对象而不需要所有对象--比如可赋值的引用。  任何指针都必须声明为Strong或者Weak,并且语言应该来关注类型转换的规定。例如,你不可以将Weak Pointer传递到一个需要Strong Pointer的地方,但是相反却可以。Push方法可以接受一个Strong Pointer并且将它转移到Stack中的Strong Pointer的序列中。Pop方法将会返回一个Strong Pointer。把Strong Pointer的引入语言将会使垃圾回收成为历史。  这里还有一个小问题--修改C++标准几乎和竞选美国总统一样容易。当我将我的注意告诉给Bjarne Stroutrup的时候,他看我的眼神好像是我刚刚要向他借一千美元一样。  然后我突然想到一个念头。我可以自己实现Strong Pointers。毕竟,它们都很想Smart Pointers。给它们一个拷贝构造函数并重载赋值操作符并不是一个大问题。事实上,这正是标准库中的auto_ptr有的。重要的是对这些操作给出一个资源转移的语法,但是这也不是很难。template <class T>SPtr<T>::SPtr (SPtr<T> & ptr){   _p = ptr.Release ();}template <class T>void SPtr<T>::operator = (SPtr<T> & ptr){   if (_p != ptr._p)   {      delete _p;      _p = ptr.Release ();   }}使这整个想法迅速成功的原因之一是我可以以值方式传递这种封装指针!我有了我的蛋糕,并且也可以吃了。看这个Stack的新的实现:class Stack{   enum { maxStack = 3 };public:   Stack ()   : _top (0)   {}   void Push (SPtr<Item> & item) throw (char *)   {      if (_top >= maxStack)      throw "Stack overflow";      _arr [_top++] = item;   }   SPtr<Item> Pop ()   {      if (_top == 0)         return SPtr<Item> ();      return _arr [--_top];   }private   int _top;   SPtr<Item> _arr [maxStack];};Pop方法强制客户将其返回值赋给一个Strong Pointer,SPtr。任何试图将他对一个普通指针的赋值都会产生一个编译期错误,因为类型不匹配。此外,因为Pop以值方式返回一个Strong Pointer(在Pop的声明时SPtr后面没有&符号),编译器在return时自动进行了一个资源转换。他调用了operator =来从数组中提取一个Item,拷贝构造函数将他传递给调用者。调用者最后拥有了指向Pop赋值的Strong Pointer指向的一个Item。我马上意识到我已经在某些东西之上了。我开始用了新的方法重写原来的代码。分析器(Parser)  我过去有一个老的算术操作分析器,是用老的资源管理的技术写的。分析器的作用是在分析树中生成节点,节点是动态分配的。例如分析器的Expression方法生成一个表达式节点。我没有时间用Strong Pointer去重写这个分析器。我令Expression、Term和Factor方法以传值的方式将Strong Pointer返回到Node中。看下面的Expression方法的实现:SPtr<Node> Parser::Expression(){   // Parse a term   SPtr<Node> pNode = Term ();   EToken token = _scanner.Token();   if ( token == tPlus || token == tMinus )   {      // Expr := Term { ('+' | '-') Term }      SPtr<MultiNode> pMultiNode = new SumNode (pNode);      do      {         _scanner.Accept();         SPtr<Node> pRight = Term ();         pMultiNode->AddChild (pRight, (token == tPlus));         token = _scanner.Token();      } while (token == tPlus || token == tMinus);      pNode = up_cast<Node, MultiNode> (pMultiNode);   }   // otherwise Expr := Term   return pNode; // by value!}最开始,Term方法被调用。他传值返回一个指向Node的Strong Pointer并且立刻把它保存到我们自己的Strong Pointer,pNode中。如果下一个符号不是加号或者减号,我们就简单的把这个SPtr以值返回,这样就释放了Node的所有权。另外一方面,如果下一个符号是加号或者减号,我们创建一个新的SumMode并且立刻(直接传递)将它储存到MultiNode的一个Strong Pointer中。这里,SumNode是从MultiMode中继承而来的,而MulitNode是从Node继承而来的。原来的Node的所有权转给了SumNode。  只要是他们在被加号和减号分开的时候,我们就不断的创建terms,我们将这些term转移到我们的MultiNode中,同时MultiNode得到了所有权。最后,我们将指向MultiNode的Strong Pointer向上映射为指向Mode的Strong Pointer,并且将他返回调用着。  我们需要对Strong Pointers进行显式的向上映射,即使指针是被隐式的封装。例如,一个MultiNode是一个Node,但是相同的is-a关系在SPtr和SPtr之间并不存在,因为它们是分离的类(模板实例)并不存在继承关系。up-cast模板是像下面这样定义的:template<class To, class From>inline SPtr<To> up_cast (SPtr<From> & from){   return SPtr<To> (from.Release ());}如果你的编译器支持新加入标准的成员模板(member template)的话,你可以为SPtr定义一个新的构造函数用来从接受一个class U。template <class T>template <class U> SPtr<T>::SPtr (SPrt<U> & uptr): _p (uptr.Release ()){}这里的这个花招是模板在U不是T的子类的时候就不会编译成功(换句话说,只在U is-a T的时候才会编译)。这是因为uptr的缘故。Release()方法返回一个指向U的指针,并被赋值为_p,一个指向T的指针。所以如果U不是一个T的话,赋值会导致一个编译时刻错误。