16.7.2. PStash con iteradores

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.