Smart Pointers
我们至今还没有讨论最常见类型的资源--用操作符new分配,此后用指针访问的一个对象。我们需要为每个对象分别定义一个封装类吗?(事实上,C++标准模板库已经有了一个模板类,叫做auto_ptr,其作用就是提供这种封装。我们一会儿在回到auto_ptr。)让我们从一个极其简单、呆板但安全的东西开始。看下面的Smart Pointer模板类,它十分坚固,甚至无法实现。
template <class T> class SPtr { public: ~SPtr () { delete _p; } T * operator->() { return _p; } T const * operator->() const { return _p; } protected: SPtr (): _p (0) {} explicit SPtr (T* p): _p (p) {} T * _p; }; |
为什么要把SPtr的构造函数设计为protected呢?如果我需要遵守第一条规则,那么我就必须这样做。资源--在这里是class T的一个对象--必须在封装器的构造函数中分配。但是我不能只简单的调用new T,因为我不知道T的构造函数的参数。因为,在原则上,每一个T都有一个不同的构造函数;我需要为他定义个另外一个封装器。模板的用处会很大,为每一个新的类,我可以通过继承SPtr定义一个新的封装器,并且提供一个特定的构造函数。
class SItem: public SPtr<Item> { public: explicit SItem (int i) : SPtr<Item> (new Item (i)) {} }; |
为每一个类提供一个Smart Pointer真的值得吗?说实话--不!他很有教学的价值,但是一旦你学会如何遵循第一规则的话,你就可以放松规则并使用一些高级的技术。这一技术是让SPtr的构造函数成为public,但是只是是用它来做资源转换(Resource Transfer)我的意思是用new操作符的结果直接作为SPtr的构造函数的参数,像这样: SPtr<Item> item (new Item (i)); 这个方法明显更需要自控性,不只是你,而且包括你的程序小组的每个成员。他们都必须发誓出了作资源转换外不把构造函数用在人以其他用途。幸运的是,这条规矩很容易得以加强。只需要在源文件中查找所有的new即可。
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<Item>。任何试图将他对一个普通指针的赋值都会产生一个编译期错误,因为类型不匹配。此外,因为Pop以值方式返回一个Strong Pointer(在Pop的声明时SPtr<Item>后面没有&符号),编译器在return时自动进行了一个资源转换。他调用了operator =来从数组中提取一个Item,拷贝构造函数将他传递给调用者。调用者最后拥有了指向Pop赋值的Strong Pointer指向的一个Item。 我马上意识到我已经在某些东西之上了。我开始用了新的方法重写原来的代码。  
2/2 首页 上一页 1 2 |