6.4. Stash con constructores y destructores

Los ejemplos de los capítulos anteriores tienen funciones que tienen correspondencia directa con constructores y destructores: initialize() y cleanup(). Éste es el fichero de cabecera de Stash, utilizando constructor y destructor:

//: C06:Stash2.h
// With constructors & destructors
#ifndef STASH2_H
#define STASH2_H

class Stash {
  int size;      // Size of each space
  int quantity;  // Number of storage spaces
  int next;      // Next empty space
  // Dynamically allocated array of bytes:
  unsigned char* storage;
  void inflate(int increase);
public:
  Stash(int size);
  ~Stash();
  int add(void* element);
  void* fetch(int index);
  int count();
};
#endif // STASH2_H ///:~

Listado 6.4. C06/Stash2.h


Las únicas definiciones de métodos que han cambiado son initialize() y cleanup(), que han sido reemplazadas con un constructor y un destructor.

//: C06:Stash2.cpp {O}
// Constructors & destructors
#include "Stash2.h"
#include "../require.h"
#include <iostream>
#include <cassert>
using namespace std;
const int increment = 100;

Stash::Stash(int sz) {
  size = sz;
  quantity = 0;
  storage = 0;
  next = 0;
}

int Stash::add(void* element) {
  if(next >= quantity) // Enough space left?
    inflate(increment);
  // Copy element into storage,
  // starting at next empty space:
  int startBytes = next * size;
  unsigned char* e = (unsigned char*)element;
  for(int i = 0; i < size; i++)
    storage[startBytes + i] = e[i];
  next++;
  return(next - 1); // Index number
}

void* Stash::fetch(int index) {
  require(0 <= index, "Stash::fetch (-)index");
  if(index >= next)
    return 0; // To indicate the end
  // Produce pointer to desired element:
  return &(storage[index * size]);
}

int Stash::count() {
  return next; // Number of elements in CStash
}

void Stash::inflate(int increase) {
  require(increase > 0, 
    "Stash::inflate zero or negative increase");
  int newQuantity = quantity + increase;
  int newBytes = newQuantity * size;
  int oldBytes = quantity * size;
  unsigned char* b = new unsigned char[newBytes];
  for(int i = 0; i < oldBytes; i++)
    b[i] = storage[i]; // Copy old to new
  delete [](storage); // Old storage
  storage = b; // Point to new memory
  quantity = newQuantity;
}

Stash::~Stash() {
  if(storage != 0) {
   cout << "freeing storage" << endl;
   delete []storage;
  }
} ///:~

Listado 6.5. C06/Stash2.cpp


Puede ver que las funciones de require.h se usan para vigilar errores del programador, en lugar de assert(). La salida de un assert() fallido no es tan útil como las funciones de require.h (que se verán más adelante en el libro).

Dado que inflate() es privado, el único modo en que require() podría fallar sería si uno de los otros miembros pasara accidentalmente un valor incorrecto a inflate(). Si está seguro de que eso no puede pasar, debería considerar eliminar el require(), pero debería tener en mente que hasta que la clase sea estable, siempre existe la posibilidad de que el código nuevo añadido a la clase podría provocar errores. El coste de require() es bajo (y podría ser eliminado automáticamente por el preprocesador) mientras que la robustez del código es alta.

Fijese cómo en el siguiente programa de prueba la definición de los objetos Stash aparece justo antes de necesitarse, y cómo la inicialización aparece como parte de la definición, en la lista de argumentos del constructor.

//: C06:Stash2Test.cpp
//{L} Stash2
// Constructors & destructors
#include "Stash2.h"
#include "../require.h"
#include <fstream>
#include <iostream>
#include <string>
using namespace std;

int main() {
  Stash intStash(sizeof(int));
  for(int i = 0; i < 100; i++)
    intStash.add(&i);
  for(int j = 0; j < intStash.count(); j++)
    cout << "intStash.fetch(" << j << ") = "
         << *(int*)intStash.fetch(j)
         << endl;
  const int bufsize = 80;
  Stash stringStash(sizeof(char) * bufsize);
  ifstream in("Stash2Test.cpp");
  assure(in, " Stash2Test.cpp");
  string line;
  while(getline(in, line))
    stringStash.add((char*)line.c_str());
  int k = 0;
  char* cp;
  while((cp = (char*)stringStash.fetch(k++))!=0)
    cout << "stringStash.fetch(" << k << ") = "
         << cp << endl;
} ///:~

Listado 6.6. C06/Stash2Test.cpp


También observe que se han eliminado llamadas a cleanup(), pero los destructores se llaman automáticamente cuando intStash y stringStash salen del ámbito.

Una cosa de la que debe ser consciente en los ejemplos con Stash: Tengo mucho cuidado usando sólo tipos básicos; es decir, aquellos sin destructores. Si intenta copiar objetos dentro de Stash, aparecerán todo tipo de problemas y no funcionará bien. En realidad la Librería Estándar de C++ puede hacer copias correctas de objetos en sus contenedores, pero es un proceso bastante sucio y complicado. En el siguiente ejemplo de Stack, verá que se utilizan punteros para esquivar esta cuestión, y en un capítulo posterior Stash también se convertirá para que use punteros.