📄 pat5d.htm
字号:
<A NAME="auto1043"></A><P></P><A NAME="iter-use-poly"></A><LI><EM>Using polymorphic iterators in C++.</EM>Polymorphic iterators have their cost. They require the iteratorobject to be allocated dynamically by a factory method. Hence theyshould be used only when there's a need for polymorphism. Otherwiseuse concrete iterators, which can be allocated on the stack.<A NAME="auto1044"></A><P>Polymorphic iterators have another drawback: the client is responsiblefor deleting them. This is error-prone, because it's easy to forgetto free a heap-allocated iterator object when you're finished with it.That's especially likely when there are multiple exit points in anoperation. And if an exception is triggered, the iterator object willnever be freed.</P><A NAME="proxy-w-iter"></A><P>The <A HREF="pat4gfs.htm" TARGET="_mainDisplayFrame">Proxy (207)</A> pattern provides a remedy. We can use astack-allocated proxy as a stand-in for the real iterator. The proxydeletes the iterator in its destructor. Thus when the proxy goes outof scope, the real iterator will get deallocated along with it. Theproxy ensures proper cleanup, even in the face of exceptions. Thisis an application of the well-known C++ technique "resourceallocation is initialization" [<A HREF="bibfs.htm#c++_arm" TARGET="_mainDisplayFrame">ES90</A>]. The Sample Code givesan example.</P></LI><A NAME="auto1045"></A><P></P><A NAME="friend-iter"></A><LI><EM>Iterators may have privileged access.</EM>An iterator can be viewed as an extension of the aggregate thatcreated it. The iterator and the aggregate are tightly coupled. Wecan express this close relationship in C++ by making the iterator a<CODE>friend</CODE> of its aggregate. Then you don't need todefine aggregate operations whose sole purpose is to let iteratorsimplement traversal efficiently.<A NAME="auto1046"></A><P>However, such privileged access can make defining new traversalsdifficult, since it'll require changing the aggregate interface to addanother friend. To avoid this problem, the Iterator class can include<CODE>protected</CODE> operations for accessing important but publiclyunavailable members of the aggregate. Iterator subclasses (and <EM>only</EM> Iterator subclasses) may use these protected operations to gainprivileged access to the aggregate.</P></LI><A NAME="auto1047"></A><P></P><A NAME="iter-recur"></A><LI><EM>Iterators for composites.</EM>External iterators can be difficult to implement over recursiveaggregate structures like those in the <A HREF="pat4cfs.htm" TARGET="_mainDisplayFrame">Composite (163)</A>pattern, because a position in the structure may span many levels ofnested aggregates. Therefore an external iterator has to store a paththrough the Composite to keep track of the current object. Sometimesit's easier just to use an internal iterator. It can record thecurrent position simply by calling itself recursively, thereby storingthe path implicitly in the call stack.<A NAME="auto1048"></A><P>If the nodes in a Composite have an interface for moving from a nodeto its siblings, parents, and children, then a cursor-based iteratormay offer a better alternative. The cursor only needs to keep track ofthe current node; it can rely on the node interface to traverse theComposite.</P><A NAME="trav-in-pre-post"></A><P>Composites often need to be traversed in more than one way. Preorder,postorder, inorder, and breadth-first traversals are common. You cansupport each kind of traversal with a different class of iterator.</P></LI><A NAME="auto1049"></A><P></P><A NAME="NullIterator"></A><LI><EM>Null iterators.</EM>A <STRONG>NullIterator</STRONG> is a degenerate iterator that's helpful forhandling boundary conditions. By definition, a NullIterator is <EM>always</EM> done with traversal; that is, its IsDone operation alwaysevaluates to true.<A NAME="auto1050"></A><P>NullIterator can make traversing tree-structured aggregates (likeComposites) easier. At each point in the traversal, we ask thecurrent element for an iterator for its children. Aggregate elementsreturn a concrete iterator as usual. But leaf elements return aninstance of NullIterator. That lets us implement traversal over theentire structure in a uniform way.</P></LI></OL><A NAME="samplecode"><A><H2><A HREF="#knownuses"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Known Uses"></A> Sample Code</H2> <A NAME="auto1051"></A><P>We'll look at the implementation of a simple List class, which is partof our foundation library(<A HREF="chapCfs.htm" TARGET="_mainDisplayFrame">Appendix C</A>).We'llshow two Iterator implementations, one for traversing the List infront-to-back order, and another for traversing back-to-front (thefoundation library supports only the first one). Then we show how touse these iterators and how to avoid committing to a particularimplementation. After that, we change the design to make sureiterators get deleted properly. The last example illustrates aninternal iterator and compares it to its external counterpart.</P><OL><A NAME="iter-interface2"></A><A NAME="list-interface"></A><LI><EM>List and Iterator interfaces.</EM>First let's look at the part of the List interface that's relevant toimplementing iterators. Refer to(<A HREF="chapCfs.htm" TARGET="_mainDisplayFrame">Appendix C</A>).for the full interface.<A NAME="auto1052"></A><PRE> template <class Item> class List { public: List(long size = DEFAULT_LIST_CAPACITY); long Count() const; Item& Get(long index) const; // ... };</PRE><A NAME="auto1053"></A><P>The <CODE>List</CODE> class provides a reasonably efficient way tosupport iteration through its public interface. It's sufficient toimplement both traversals. So there's no need to give iteratorsprivileged access to the underlying data structure; that is, theiterator classes are not friends of <CODE>List</CODE>. To enabletransparent use of the different traversals we define an abstract<CODE>Iterator</CODE> class, which defines the iterator interface.</P><A NAME="class_Iterator_declaration"></A><PRE> template <class Item> class Iterator { public: virtual void First() = 0; virtual void Next() = 0; virtual bool IsDone() const = 0; virtual Item CurrentItem() const = 0; protected: Iterator(); };</PRE></LI><A NAME="auto1054"></A><P></P><A NAME="auto1055"></A><LI><EM>Iterator subclass implementations.</EM><CODE>ListIterator</CODE> is a subclass of <CODE>Iterator</CODE>.<A NAME="auto1056"></A><PRE> template <class Item> class ListIterator : public Iterator<Item> { public: ListIterator(const List<Item>* aList); virtual void First(); virtual void Next(); virtual bool IsDone() const; virtual Item CurrentItem() const; private: const List<Item>* _list; long _current; };</PRE><A NAME="list-iter2"></A><P>The implementation of <CODE>ListIterator</CODE> is straightforward. Itstores the <CODE>List</CODE> along with an index <CODE>_current</CODE> intothe list:</P><A NAME="auto1057"></A><PRE> template <class Item> ListIterator<Item>::ListIterator ( const List<Item>* aList ) : _list(aList), _current(0) { }</PRE><A NAME="auto1058"></A><P><CODE>First</CODE> positions the iterator to the first element:</P><A NAME="auto1059"></A><PRE> template <class Item> void ListIterator<Item>::First () { _current = 0; }</PRE><A NAME="auto1060"></A><P><CODE>Next</CODE> advances the current element:</P><A NAME="auto1061"></A><PRE> template <class Item> void ListIterator<Item>::Next () { _current++; }</PRE><A NAME="auto1062"></A><P><CODE>IsDone</CODE> checks whether the index refers to an element withinthe List:</P><A NAME="auto1063"></A><PRE> template <class Item> bool ListIterator<Item>::IsDone () const { return _current >= _list->Count(); }</PRE><A NAME="auto1064"></A><P>Finally, <CODE>CurrentItem</CODE> returns the item at the current index.If the iteration has already terminated, then we throw an<CODE>IteratorOutOfBounds</CODE> exception:</P><A NAME="auto1065"></A><PRE> template <class Item> Item ListIterator<Item>::CurrentItem () const { if (IsDone()) { throw IteratorOutOfBounds; } return _list->Get(_current); }</PRE><A NAME="auto1066"></A><P>The implementation of ReverseListIterator is identical, except its<CODE>First</CODE> operation positions <CODE>_current</CODE>to the end of the list, and <CODE>Next</CODE> decrements<CODE>_current</CODE> toward the first item.</P></LI><A NAME="auto1067"></A><P></P><A NAME="auto1068"></A><LI><EM>Using the iterators.</EM>Let's assume we have a <CODE>List</CODE> of <CODE>Employee</CODE> objects,and we would like to print all the contained employees. The<CODE>Employee</CODE> class supports this with a <CODE>Print</CODE>operation. To print the list, we define a <CODE>PrintEmployees</CODE>operation that takes an iterator as an argument. It uses the iteratorto traverse and print the list.<A NAME="auto1069"></A><PRE> void PrintEmployees (Iterator<Employee*>& i) { for (i.First(); !i.IsDone(); i.Next()) { i.CurrentItem()->Print(); } }</PRE><A NAME="auto1070"></A><P>Since we have iterators for both back-to-front and front-to-backtraversals, we can reuse this operation to print the employees in bothorders.</P><A NAME="auto1071"></A><PRE> List<Employee*>* employees; // ... ListIterator<Employee*> forward(employees); ReverseListIterator<Employee*> backward(employees); PrintEmployees(forward); PrintEmployees(backward);</PRE></LI><A NAME="skiplist-265"></A><LI><EM>Avoiding commitment to a specific list implementation.</EM>Let's consider how a skiplist variation of <CODE>List</CODE> would affectour iteration code. A <CODE>SkipList</CODE> subclass of<CODE>List</CODE> must provide a <CODE>SkipListIterator</CODE> thatimplements the <CODE>Iterator</CODE> interface. Internally, the<CODE>SkipListIterator</CODE> has to keep more than just an index todo the iteration efficiently. But since<CODE>SkipListIterator</CODE> conforms to the<CODE>Iterator</CODE> interface, the <CODE>PrintEmployees</CODE> operationcan also be used when the employees are stored in a <CODE>SkipList</CODE>object.<A NAME="auto1072"></A><PRE> SkipList<Employee*>* employees; // ... SkipListIterator<Employee*> iterator(employees); PrintEmployees(iterator);</PRE><A NAME="auto1073"></A><P>Although this approach works, it would be better if we didn't have to committo a specific <CODE>List</CODE> implementation, namely<CODE>SkipList</CODE>. We can introduce an <CODE>AbstractList</CODE>class to standardize the list interface for different listimplementations. <CODE>List</CODE> and <CODE>SkipList</CODE> becomesubclasses of <CODE>AbstractList</CODE>.<A NAME="iter-poly-enable"></A><P>To enable polymorphic iteration, <CODE>AbstractList</CODE> defines afactory method <CODE>CreateIterator</CODE>, which subclasses override toreturn their corresponding iterator:</P><A NAME="auto1074"></A><PRE> template <class Item> class AbstractList { public: virtual Iterator<Item>* CreateIterator() const = 0; // ... };</PRE><A NAME="auto1075"></A><P>An alternative would be to define a general mixin class<CODE>Traversable</CODE> that defines the interface for creating aniterator. Aggregate classes can mix in<CODE>Traversable</CODE> to support polymorphic iteration.<A NAME="auto1076"></A><P><CODE>List</CODE> overrides <CODE>CreateIterator</CODE> to return a<CODE>ListIterator</CODE> object:</P><A NAME="auto1077"></A><PRE> template <class Item> Iterator<Item>* List<Item>::CreateIterator () const { return new ListIterator<Item>(this); }</PRE><A NAME="auto1078"></A><P>Now we're in a position to write the code for printingthe employees independent of a concrete representation.</P><A NAME="auto1079"></A><PRE> // we know only that we have an AbstractList AbstractList<Employee*>* employees; // ... Iterator<Employee*>* iterator = employees->CreateIterator(); PrintEmployees(*iterator); delete iterator;</PRE>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -