Para la mayoría de los contenedores tiene sentido tener un
iterador. Aquí tenemos un iterador añadido a la clase
PStash
:
//: C16:TPStash2.h // Templatized PStash with nested iterator #ifndef TPSTASH2_H #define TPSTASH2_H #include "../require.h" #include <cstdlib> template<class T, int incr = 20> class PStash { int quantity; int next; T** storage; void inflate(int increase = incr); public: PStash() : quantity(0), storage(0), next(0) {} ~PStash(); int add(T* element); T* operator[](int index) const; T* remove(int index); int count() const { return next; } // Nested iterator class: class iterator; // Declaration required friend class iterator; // Make it a friend class iterator { // Now define it PStash& ps; int index; public: iterator(PStash& pStash) : ps(pStash), index(0) {} // To create the end sentinel: iterator(PStash& pStash, bool) : ps(pStash), index(ps.next) {} // Copy-constructor: iterator(const iterator& rv) : ps(rv.ps), index(rv.index) {} iterator& operator=(const iterator& rv) { ps = rv.ps; index = rv.index; return *this; } iterator& operator++() { require(++index <= ps.next, "PStash::iterator::operator++ " "moves index out of bounds"); return *this; } iterator& operator++(int) { return operator++(); } iterator& operator--() { require(--index >= 0, "PStash::iterator::operator-- " "moves index out of bounds"); return *this; } iterator& operator--(int) { return operator--(); } // Jump interator forward or backward: iterator& operator+=(int amount) { require(index + amount < ps.next && index + amount >= 0, "PStash::iterator::operator+= " "attempt to index out of bounds"); index += amount; return *this; } iterator& operator-=(int amount) { require(index - amount < ps.next && index - amount >= 0, "PStash::iterator::operator-= " "attempt to index out of bounds"); index -= amount; return *this; } // Create a new iterator that's moved forward iterator operator+(int amount) const { iterator ret(*this); ret += amount; // op+= does bounds check return ret; } T* current() const { return ps.storage[index]; } T* operator*() const { return current(); } T* operator->() const { require(ps.storage[index] != 0, "PStash::iterator::operator->returns 0"); return current(); } // Remove the current element: T* remove(){ return ps.remove(index); } // Comparison tests for end: bool operator==(const iterator& rv) const { return index == rv.index; } bool operator!=(const iterator& rv) const { return index != rv.index; } }; iterator begin() { return iterator(*this); } iterator end() { return iterator(*this, true);} }; // Destruction of contained objects: template<class T, int incr> PStash<T, incr>::~PStash() { for(int i = 0; i < next; i++) { delete storage[i]; // Null pointers OK storage[i] = 0; // Just to be safe } delete []storage; } template<class T, int incr> int PStash<T, incr>::add(T* element) { if(next >= quantity) inflate(); storage[next++] = element; return(next - 1); // Index number } template<class T, int incr> inline T* PStash<T, incr>::operator[](int index) const { require(index >= 0, "PStash::operator[] index negative"); if(index >= next) return 0; // To indicate the end require(storage[index] != 0, "PStash::operator[] returned null pointer"); return storage[index]; } template<class T, int incr> T* PStash<T, incr>::remove(int index) { // operator[] performs validity checks: T* v = operator[](index); // "Remove" the pointer: storage[index] = 0; return v; } template<class T, int incr> void PStash<T, incr>::inflate(int increase) { const int tsz = sizeof(T*); T** st = new T*[quantity + increase]; memset(st, 0, (quantity + increase) * tsz); memcpy(st, storage, quantity * tsz); quantity += increase; delete []storage; // Old storage storage = st; // Point to new memory } #endif // TPSTASH2_H ///:~
Listado 16.27. C16/TPStash2.h
La mayoría de este archivo es un traducción prácticamente
directa del anterior PStash
y el iterador
anidado dentro de un template. Esta vez, sin embargo, el
operador devuelve referencias al iterador actual, la cual es
una aproximación más típica y flexible.
El destructor llama a delete
para todos
los punteros que contiene, y como el tipo es obtenido de la
plantilla, se ejecutará la destrucción adecuada. Hay que
estar precavido que si el contenedor controla punteros al tipo
de la clase base, este tipo debe tener un destructor virtual
para asegurar un limpiado
adecuado de los objetos derivados que hayan usado un upcast
cuando se los alojó en el contenedor.
El PStash::iterator
mantiene el modelo de
engancharse a un único objeto contenedor durante su ciclo de
vida. Además, el constructor de copia permite crear un nuevo
iterador que apunte a la misma posición del iterador desde el
que se le creo, creando de esta manera un marcador dentro del
contenedor. Las funciones miembro operator+=
y el operator-=
permiten
mover un iterador un número de posiciones, mientras se
respeten los límites del contenedor. Los operadores
sobrecargados de incremento y decremento mueven el iterador
una posición. El operator+
produce un
nuevo iterador que se mueve adelante la cantidad añadida. Como
en el ejemplo anterior, los operadores de desreferencia de
punteros son usados para manejar el elemento al que el
iterador está referenciando, y remove()
destruye el objeto actual llamando al
remove()
del contenedor.
Se usa la misma clase de código de antes para crear el
marcador final: un segundo constructor, la función miembro del
contenedor end()
, y el
operator==
y operator!=
para comparaciones.
El siguiente ejemplo crea y comprueba dos diferentes clases de
objetos Stash
, uno para una nueva clase
llamada Int
que anuncia su construcción y
destrucción y otra que gestiona objetos string
de la librería Estándar.
//: C16:TPStash2Test.cpp #include "TPStash2.h" #include "../require.h" #include <iostream> #include <vector> #include <string> using namespace std; class Int { int i; public: Int(int ii = 0) : i(ii) { cout << ">" << i << ' '; } ~Int() { cout << "~" << i << ' '; } operator int() const { return i; } friend ostream& operator<<(ostream& os, const Int& x) { return os << "Int: " << x.i; } friend ostream& operator<<(ostream& os, const Int* x) { return os << "Int: " << x->i; } }; int main() { { // To force destructor call PStash<Int> ints; for(int i = 0; i < 30; i++) ints.add(new Int(i)); cout << endl; PStash<Int>::iterator it = ints.begin(); it += 5; PStash<Int>::iterator it2 = it + 10; for(; it != it2; it++) delete it.remove(); // Default removal cout << endl; for(it = ints.begin();it != ints.end();it++) if(*it) // Remove() causes "holes" cout << *it << endl; } // "ints" destructor called here cout << "\n-------------------\n"; ifstream in("TPStash2Test.cpp"); assure(in, "TPStash2Test.cpp"); // Instantiate for String: PStash<string> strings; string line; while(getline(in, line)) strings.add(new string(line)); PStash<string>::iterator sit = strings.begin(); for(; sit != strings.end(); sit++) cout << **sit << endl; sit = strings.begin(); int n = 26; sit += n; for(; sit != strings.end(); sit++) cout << n++ << ": " << **sit << endl; } ///:~
Listado 16.28. C16/TPStash2Test.cpp
Por conveniencia Int
tiene asociado un
ostream operator<<
para
Int&
y Int*
.
El primer bloque de código en main()
está
rodeado de llaves para forzar la destrucción de
PStash<Int>
que produce un limpiado
automático por este destructor. Unos cuantos elementos son
sacados y borrados a mano para mostrar que PStash
limpia el resto.
Para ambas instancias de PStash
, se
crea un iterador y se usa para moverse a través del
contenedor. Note la elegancia generada por el uso de estos
constructores; no hay que preocuparse por los detalles de
implementación de usar un array. Se le dice al contenedor y al
iterador qué hacer y no
cómo hacerlo. Esto produce una solución
más sencilla de conceptualizar, construir y modificar.