In last month's Borland C++ Developer's Journal, we described the basic design principles behind iterator classes and we showed you how to implement forward and reverse iterator classes for your own container classes (C++ Programming - An introduction to iterator classes). Unfortunately, writing your own container classes from scratch is a time-consuming and error-prone process.
In this article, we'll show you how Borland's Object-based
Container class library (in which all classes derive from the
Object class) implements iterators for the container
classes, and how you can use these iterators directly or indirectly
in your own programs. In a future issue, we'll close this
series by showing you how you can use the template-based BIDS
class library's iterator classes.
In the ITERATOR.EXE program from last month's article, you'll recall that we created an integer array class, a class to iterate forward through the array, and a class to iterate through the array in the opposite direction. To keep the design of the integer array class simple, we didn't include any iterator member functions in the class.
In contrast, many of Borland's container classes combine iterator member functions with iterator classes. If a container class creates its own iterator object when you call an iterator member function, that iterator object is known as an internal iterator. Many of the container classes in the Borland class libraries use internal iterators to implement iterating functions.
For example, if you create a test function that you want to apply to each item in a container, you can call the firstThat() or the lastThat() member function of the Container class. The member function will call that test function for each item in the container and return a reference to the first or last object that matches the test condition. To implement these functions, the container creates an internal iterator object, uses the iterator, and then destroys it before the function returns the result.
If, instead, you want to create an iterator object that exists
independently of its container object, it's called an external
iterator. The container classes that promote this in Borland's
Container class libraries provide an initIterator() member
function that creates an external iterator and associates it with
its corresponding container object.
When you're designing an iterator class that allows external iterator objects, you'll need to decide whether the container or the iterator will keep track of the iterator's position in the container. An intrusive iterator relies on the container class to maintain the current iterator position. A non-intrusive iterator keeps track of its own position. Unfortunately, intrusive iterators make it very difficult to manage multiple, simultaneous iterators; since the container maintains the iterator position, each iterator has to be in the same position in the container.
Because certain operations require that you use the least possible
communication (i.e. function calls) between the iterator and the
container, an intrusive iterator is sometimes necessary. However,
when a general-purpose approach is more important than absolute
speed, iterators that maintain the current iterator position themselves
(non-intrusive iterators) are more common. Since Borland wanted
the Container class library to be useful in many situations, all
the iterator types in the Container class library are non-intrusive
iterators.
In last month's article, we created a forward iterator class first and then derived the reverse iterator class from it. By doing this, we kept the interface to the iterator classes simpleto iterate, we simply called the function call operator.
In keeping the interface simple, though, we made it impossible to change the iteration direction without creating an iterator of the opposite class. In contrast, Borland overloads the pre- and post-increment operators for forward iterators. Then, instead of creating a specific class for reverse iteration, Borland sometimes derives new classes from the forward iterator classes. The derived iterators provide iteration in either direction by overloading the pre- and post-decrement operators as well.
Overloading an iterator class's pre- and post-increment
or pre- and post-decrement operators lets you move an iterator
to point to the next item before or after returning the current
item. While this increases the complexity of the class's
interface somewhat, it adds to the flexibility you have when you're
adjusting the position of the iterator.
Let's create a program similar in function to last month's ITERATOR.EXE. However, in this program, we'll take advantage of some classes from Borland's Container class library.
To begin, launch the Borland C++ Integrated Development Environment (IDE) 3.1 for DOS. When the IDE's main menu bar and desktop appear, choose New from the File menu and enter the code from Listing A.
Listing A: STRNITER.CPP
#include <dbllist.h> #include <strng.h> #include <iostream.h> int main() { DoubleList list; list.addAtTail(*(new String("a"))); list.addAtTail(*(new String("b"))); list.addAtTail(*(new String("c"))); list.addAtTail(*(new String("d"))); list.addAtTail(*(new String("e"))); list.addAtTail(*(new String("f"))); ContainerIterator& forwardIterator = list.initIterator(); DoubleListIterator reverseIterator(list, 0); cout << "FWD REV" << endl; while(forwardIterator && reverseIterator) { cout << " " << forwardIterator++; cout << " " << reverseIterator-- << endl; } return 0; }
When you finish entering the code, choose Save from the File Menu. Then, in the Save File As entry field of the Save File As dialog box, enter STRNITER.CPP and click OK.
Next, choose Linker from the Options menu and then choose Libraries... from the Linker submenu. When the Libraries dialog box appears, select the Static radio button in the Container Class Library group to enable the Linker to link in the correct functions from this library. Click OK to save this setting.
Next, choose Directories... from the Options menu. In
the Include Directories entry field, add the path
\BORLANDC\CLASSLIB\INCLUDE;
In the Libraries Directories entry field, make sure that
\BORLANDC\CLASSLIB\LIB;
is one of the paths shown. If necessary, enter this path (add ; to the end of the previous path). When you finish, click OK.
To build the program, choose Compile from the Compile menu. When the Linker finishes linking the program, choose Quit from the File menu to quit the IDE.
When the DOS prompt appears, enter STRNITER. Figure A shows the output you should see from this program.
Figure A - The output from the STRNITER.EXE program illustrates the use of simultaneous iterators on a doubly linked list.
FWD REV a f b e c d d c e b f a
In this program, we use the DoubleList class to implement a doubly linked list of String objects (the String class derives from the TObject class). After adding six text strings, we then create a forward iterator (a ContainerIterator object) and a reverse iterator (a DoubleListIterator object).
Finally, we use a simple while() loop to display each
item in the list. Because each iterator's class defines
an integer conversion function that returns 1 when the iterator
is pointing to a valid object, the while() loop can contain
the symbol names themselves to test for an end-of-list condition.
Inside the loop, the overloaded versions of the post-increment
and post-decrement operators return the corresponding iterator's
current item in the list and then increment or decrement
the iterator.
Iterator classes and their corresponding container classes can
be very powerful. By learning to use iterator and container classes
that are part of the Borland Container class library, you can
add a significant amount of flexibility to your designs without
writing a lot of code.
Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.