Stack with iterators
We can repeat the process with the dynamically-sized
Stack class that has been used as an example throughout the book. Here’s
the Stack class with a nested iterator folded into the mix:
//: C16:TStack2.h
// Templatized Stack with nested iterator
#ifndef TSTACK2_H
#define TSTACK2_H
template<class T> class Stack { struct Link { T* data;
Link* next;
Link(T* dat, Link* nxt)
: data(dat), next(nxt) {} }* head;
public:
Stack() : head(0) {} ~Stack();
void push(T* dat) { head = new Link(dat, head);
}
T* peek() const { return head ? head->data : 0;
}
T* pop();
// Nested iterator class:
class iterator; // Declaration required
friend class iterator; // Make it a friend
class iterator { // Now define it Stack::Link* p;
public:
iterator(const Stack<T>& tl) : p(tl.head) {} // Copy-constructor:
iterator(const iterator& tl) : p(tl.p) {} // The end sentinel iterator:
iterator() : p(0) {} // operator++ returns boolean indicating end:
bool operator++() { if(p->next)
p = p->next;
else p = 0; // Indicates end of list
return bool(p);
}
bool operator++(int) { return operator++(); } T* current() const { if(!p) return 0;
return p->data;
}
// Pointer dereference operator:
T* operator->() const { require(p != 0,
"PStack::iterator::operator->returns 0");
return current();
}
T* operator*() const { return current(); } // bool conversion for conditional test:
operator bool() const { return bool(p); } // Comparison to test for end:
bool operator==(const iterator&) const { return p == 0;
}
bool operator!=(const iterator&) const { return p != 0;
}
};
iterator begin() const { return iterator(*this);
}
iterator end() const { return iterator(); }};
template<class T> Stack<T>::~Stack() { while(head)
delete pop();
}
template<class T> T* Stack<T>::pop() { if(head == 0) return 0;
T* result = head->data;
Link* oldHead = head;
head = head->next;
delete oldHead;
return result;
}
#endif // TSTACK2_H ///:~
You’ll also notice
the class has been changed to support ownership, which works now because the
class knows the exact type (or at least the base type, which will work assuming
virtual destructors are used). The default is for the container to destroy its
objects but you are responsible for any pointers that you pop( ).
The iterator is
simple, and physically very small – the size of a single pointer. When you
create an iterator, it’s initialized to the head of the linked list, and
you can only increment it forward through the list. If you want to start over
at the beginning, you create a new iterator, and if you want to remember a spot
in the list, you create a new iterator from the existing iterator pointing at
that spot (using the iterator’s copy-constructor).
To call functions for the object referred
to by the iterator, you can use the current( ) function, the operator*, or the
pointer dereference operator-> (a common sight in iterators). The latter has an
implementation that looks identical to current( ) because it
returns a pointer to the current object, but is different because the pointer
dereference operator performs the extra levels of dereferencing (see Chapter
12).
The iterator class follows the form
you saw in the prior example. class iterator is nested inside the
container class, it contains constructors to create both an iterator pointing
at an element in the container and an “end sentinel” iterator, and the
container class has the begin( ) and end( ) methods to
produce these iterators. (When you learn the more about the Standard C++
Library, you’ll see that the names iterator, begin( ), and end( )
that are used here were clearly lifted standard container classes. At the end
of this chapter, you’ll see that this enables these container classes to be
used as if they were Standard C++ Library container classes.)
The entire implementation is contained in
the header file, so there’s no separate cpp file. Here’s a small test that
exercises the iterator:
//: C16:TStack2Test.cpp
#include "TStack2.h"
#include "../require.h"
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() { ifstream file("TStack2Test.cpp"); assure(file, "TStack2Test.cpp");
Stack<string> textlines;
// Read file and store lines in the Stack:
string line;
while(getline(file, line))
textlines.push(new string(line));
int i = 0;
// Use iterator to print lines from the list:
Stack<string>::iterator it = textlines.begin();
Stack<string>::iterator* it2 = 0;
while(it != textlines.end()) { cout << it->c_str() << endl;
it++;
if(++i == 10) // Remember 10th line
it2 = new Stack<string>::iterator(it);
}
cout << (*it2)->c_str() << endl;
delete it2;
} ///:~
A Stack is instantiated to hold string
objects and filled with lines from a file. Then an iterator is created and used
to move through the sequence. The tenth line is remembered by copy-constructing
a second iterator from the first; later this line is printed and the iterator –
created dynamically – is destroyed. Here, dynamic object creation is used to control the lifetime of the object.