📄 pat5d.htm
字号:
</LI><A NAME="iter-dest"></A><LI><EM>Making sure iterators get deleted.</EM><A NAME="clean-up_proxy_for_iterators"></A>Notice that <CODE>CreateIterator</CODE> returns a newly allocatediterator object. We're responsible for deleting it. If we forget,then we've created a storage leak. To make life easier for clients,we'll provide an <CODE>IteratorPtr</CODE> that acts as a proxy for aniterator. It takes care of cleaning up the <CODE>Iterator</CODE> objectwhen it goes out of scope.<A NAME="auto1080"></A><P><CODE>IteratorPtr</CODE> is always allocated on thestack.<A NAME="fn5"></A><A HREF="#footnote5"><SUP>5</SUP></A>C++ automatically takes care of callingits destructor, which deletes the real iterator.<CODE>IteratorPtr</CODE> overloads both<CODE>operator-></CODE> and<CODE>operator*</CODE> in such a way that an <CODE>IteratorPtr</CODE> can betreated just like a pointer to an iterator. The members of<CODE>IteratorPtr</CODE> are all implemented inline; thus they can incur nooverhead.</P><A NAME="auto1081"></A><PRE> template <class Item> class IteratorPtr { public: IteratorPtr(Iterator<Item>* i): _i(i) { } ~IteratorPtr() { delete _i; } Iterator<Item>* operator->() { return _i; } Iterator<Item>& operator*() { return *_i; } private: // disallow copy and assignment to avoid // multiple deletions of _i: IteratorPtr(const IteratorPtr&); IteratorPtr& operator=(const IteratorPtr&); private: Iterator<Item>* _i; };</PRE><A NAME="auto1082"></A><P><CODE>IteratorPtr</CODE> lets us simplify our printing code:</P><A NAME="auto1083"></A><PRE> AbstractList<Employee*>* employees; // ... IteratorPtr<Employee*> iterator(employees->CreateIterator()); PrintEmployees(*iterator);</PRE><A NAME="iter-internal"></A><LI><EM>An internal ListIterator.</EM>As a final example, let's look at a possible implementation of aninternal or passive <CODE>ListIterator</CODE> class. Here the iteratorcontrols the iteration, and it applies an operation to each element.<A NAME="iter-param"></A><P>The issue in this case is how to parameterize the iterator with theoperation we want to perform on each element. C++ does not supportanonymous functions or closures that other languages provide for thistask. There are at least two options: (1) Pass in a pointer to afunction (global or static), or (2) rely on subclassing. In the firstcase, the iterator calls the operation passed to it at each point inthe iteration. In the second case, the iterator calls an operationthat a subclass overrides to enact specific behavior.</P><A NAME="auto1084"></A><P>Neither option is perfect. Often you want to accumulate state duringthe iteration, and functions aren't well-suited to that; we would haveto use static variables to remember the state. An<CODE>Iterator</CODE> subclass provides us with a convenient place tostore the accumulated state, like in an instance variable. Butcreating a subclass for every different traversal is more work.</P><A NAME="listtrav"></A><P>Here's a sketch of the second option, which uses subclassing. We callthe internal iterator a <CODE>ListTraverser</CODE>.</P><A NAME="auto1085"></A><PRE> template <class Item> class ListTraverser { public: ListTraverser(List<Item>* aList); bool Traverse(); protected: virtual bool ProcessItem(const Item&) = 0; private: ListIterator<Item> _iterator; };</PRE><A NAME="auto1086"></A><P><CODE>ListTraverser</CODE> takes a <CODE>List</CODE> instance as a parameter.Internally it uses an external <CODE>ListIterator</CODE> to do thetraversal. <CODE>Traverse</CODE> starts the traversal and calls<CODE>ProcessItem</CODE> for each item. The internal iterator can choose toterminate a traversal by returning <CODE>false</CODE> from<CODE>ProcessItem</CODE>. <CODE>Traverse</CODE> returns whether the traversalterminated prematurely.</P><A NAME="auto1087"></A><PRE> template <class Item> ListTraverser<Item>::ListTraverser ( List<Item>* aList ) : _iterator(aList) { } template <class Item> bool ListTraverser<Item>::Traverse () { bool result = false; for ( _iterator.First(); !_iterator.IsDone(); _iterator.Next() ) { result = ProcessItem(_iterator.CurrentItem()); if (result == false) { break; } } return result; }</PRE><A NAME="auto1088"></A><P>Let's use a <CODE>ListTraverser</CODE> to print the first 10employees from our employee list. To do it we have to subclass<CODE>ListTraverser</CODE> and override <CODE>ProcessItem</CODE>. Wecount the number of printed employees in a <CODE>_count</CODE> instancevariable.</P><A NAME="auto1089"></A><PRE> class PrintNEmployees : public ListTraverser<Employee*> { public: PrintNEmployees(List<Employee*>* aList, int n) : ListTraverser<Employee*>(aList), _total(n), _count(0) { } protected: bool ProcessItem(Employee* const&); private: int _total; int _count; }; bool PrintNEmployees::ProcessItem (Employee* const& e) { _count++; e->Print(); return _count < _total; }</PRE><A NAME="auto1090"></A><P>Here's how <CODE>PrintNEmployees</CODE> prints the first 10 employeeson the list:</P><A NAME="auto1091"></A><PRE> List<Employee*>* employees; // ... PrintNEmployees pa(employees, 10); pa.Traverse();</PRE><A NAME="iter-external"></A><P>Note how the client doesn't specify the iteration loop. The entireiteration logic can be reused. This is the primary benefit of aninternal iterator. It's a bit more work than an external iterator,though, because we have to define a new class. Contrast this withusing an external iterator:</P><A NAME="auto1092"></A><PRE> ListIterator<Employee*> i(employees); int count = 0; for (i.First(); !i.IsDone(); i.Next()) { count++; i.CurrentItem()->Print(); if (count >= 10) { break; } }</PRE><A NAME="auto1093"></A><P>Internal iterators can encapsulate different kinds of iteration. Forexample, <CODE>FilteringListTraverser</CODE> encapsulates aniteration that processes only items that satisfy a test:</P><A NAME="auto1094"></A><PRE> template <class Item> class FilteringListTraverser { public: FilteringListTraverser(List<Item>* aList); bool Traverse(); protected: virtual bool ProcessItem(const Item&) = 0; virtual bool TestItem(const Item&) = 0; private: ListIterator<Item> _iterator; };</PRE><A NAME="auto1095"></A><P>This interface is the same as <CODE>ListTraverser</CODE>'s except for anadded <CODE>TestItem</CODE> member function that defines the test.Subclasses override <CODE>TestItem</CODE> to specify the test.</P><A NAME="auto1096"></A><P><CODE>Traverse</CODE> decides to continue the traversal based on theoutcome of the test:</P><A NAME="auto1097"></A><PRE> template <class Item> void FilteringListTraverser<Item>::Traverse () { bool result = false; for ( _iterator.First(); !_iterator.IsDone(); _iterator.Next() ) { if (TestItem(_iterator.CurrentItem())) { result = ProcessItem(_iterator.CurrentItem()); if (result == false) { break; } } } return result; }</PRE><A NAME="auto1098"></A><P>A variant of this class could define <CODE>Traverse</CODE> to return ifat least one item satisfies the test.<A NAME="fn6"></A><A HREF="#footnote6"><SUP>6</SUP></A></P></LI></OL><A NAME="knownuses"><A><H2><A HREF="#relatedpatterns"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Related Patterns"></A> Known Uses</H2> <A NAME="auto1099"></A><P>Iterators are common in object-oriented systems. Most collectionclass libraries offer iterators in one form or another.</P><A NAME="auto1100"></A><P>Here's an example from the Booch components [<A HREF="bibfs.htm#booch_ood" TARGET="_mainDisplayFrame">Boo94</A>], apopular collection class library. It provides both a fixed size(bounded) and dynamically growing (unbounded) implementation of aqueue. The queue interface is defined by an abstract Queue class. Tosupport polymorphic iteration over the different queueimplementations, the queue iterator is implemented in the terms of theabstract Queue class interface. This variation has the advantage thatyou don't need a factory method to ask the queue implementations fortheir appropriate iterator. However, it requires the interface of theabstract Queue class to be powerful enough to implement the iteratorefficiently.</P><A NAME="smalltalk-use-iter"></A><P>Iterators don't have to be defined as explicitly in Smalltalk. Thestandard collection classes (Bag, Set, Dictionary, OrderedCollection,String, etc.) define an internal iterator method <CODE>do:</CODE>, whichtakes a block (i.e., closure) as an argument. Each element in thecollection is bound to the local variable in the block; then the blockis executed. Smalltalk also includes a set of Stream classes thatsupport an iterator-like interface. ReadStream is essentially anIterator, and it can act as an external iterator for all thesequential collections. There are no standard external iterators fornonsequential collections such as Set and Dictionary.</P><A NAME="unidraw-use-iter"></A><P>Polymorphic iterators and the cleanup Proxy described earlier areprovided by the ET++ container classes [<A HREF="bibfs.htm#et++" TARGET="_mainDisplayFrame">WGM88</A>]. The Unidrawgraphical editing framework classes use cursor-basediterators [<A HREF="bibfs.htm#unidraw_framework" TARGET="_mainDisplayFrame">VL90</A>].</P><A NAME="auto1101"></A><P>ObjectWindows 2.0 [<A HREF="bibfs.htm#objectwindows" TARGET="_mainDisplayFrame">Bor94</A>] provides a class hierarchy ofiterators for containers. You can iterate over different containertypes in the same way. The ObjectWindow iteration syntax relies onoverloading the postincrement operator <CODE>++</CODE> to advance theiteration.</P><A NAME="relatedpatterns"></A><H2><A HREF="#last"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: navigation"></A> Related Patterns</H2> <A NAME="auto1102"></A><P><A HREF="pat4cfs.htm" TARGET="_mainDisplayFrame">Composite (163)</A>:Iterators are often applied to recursive structures such asComposites.</P><A NAME="auto1103"></A><P><A HREF="pat3cfs.htm" TARGET="_mainDisplayFrame">Factory Method (107)</A>:Polymorphic iterators rely on factory methods to instantiate theappropriate Iterator subclass.</P><A NAME="auto1104"></A><P><A HREF="pat5ffs.htm" TARGET="_mainDisplayFrame">Memento (283)</A> isoften used in conjunction with the Iterator pattern. An iteratorcan use a memento to capture the state of an iteration. The iteratorstores the memento internally.</P><A NAME="last"></A><P><A HREF="#intent"><IMG SRC="gifsb/up3.gif" BORDER=0></A><BR><A HREF="pat5efs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/rightar3.gif" ALIGN=TOP BORDER=0></A> <A HREF="pat5efs.htm" TARGET="_mainDisplayFrame">Mediator</A><BR><A HREF="pat5cfs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/leftarr3.gif" ALIGN=TOP BORDER=0></A> <A HREF="pat5cfs.htm" TARGET="_mainDisplayFrame">Interpreter</A></P><HR><A NAME="footnote2"></A><P><SUP>2</SUP>Booch refers to external and internal iterators as<STRONG>active</STRONG> and <STRONG>passive</STRONG> iterators,respectively [<A HREF="bibfs.htm#booch_ood" TARGET="_mainDisplayFrame">Boo94</A>]. Theterms "active" and "passive" describe the role of the client, notthe level of activity in the iterator.</P><A NAME="footnote3"></A><P><SUP>3</SUP>Cursors are a simple example of the <A HREF="pat5ffs.htm"TARGET="_mainDisplayFrame">Memento (283)</A> pattern and share many of itsimplementation issues.</P><A NAME="footnote4"></A><P><SUP>4</SUP>We can make this interfaceeven <EM>smaller</EM> by merging Next, IsDone, and CurrentItem into asingle operation that advances to the next object and returns it. Ifthe traversal is finished, then this operation returns a specialvalue (0, for instance) that marks the end of the iteration.</P><A NAME="footnote5"></A><P><SUP>5</SUP>You can ensure this at compile-time just by declaringprivate <CODE>new</CODE> and <CODE>delete</CODE> operators. An accompanyingimplementation isn't needed.</P><A NAME="footnote6"></A><P><SUP>6</SUP>The <CODE>Traverse</CODE> operation in these examplesis a <A HREF="pat5jfs.htm" TARGET="_mainDisplayFrame">Template Method (325)</A>with primitive operations <CODE>TestItem</CODE> and<CODE>ProcessItem</CODE>.</P></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -