9.3. Stash y Stack con inlines

Disponiendo de inlines, podemos modificar las clases Stash y Stack para hacerlas más eficientes.

//: C09:Stash4.h
// Inline functions
#ifndef STASH4_H
#define STASH4_H
#include "../require.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 sz) : size(sz), quantity(0),
    next(0), storage(0) {}
  Stash(int sz, int initQuantity) : size(sz), 
    quantity(0), next(0), storage(0) { 
    inflate(initQuantity); 
  }
  Stash::~Stash() {
    if(storage != 0) 
      delete []storage;
  }
  int add(void* element);
  void* fetch(int index) const {
    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 count() const { return next; }
};
#endif // STASH4_H ///:~

Listado 9.8. C09/Stash4.h


Obviamente las funciones pequeñas funcionan bien como inlines, pero note que las dos funciones más largas siguen siendo no-inline, dado que convertirlas a inline no representaría ninguna mejora de rendimiento.

//: C09:Stash4.cpp {O}
#include "Stash4.h"
#include <iostream>
#include <cassert>
using namespace std;
const int increment = 100;

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::inflate(int increase) {
  assert(increase >= 0);
  if(increase == 0) return;
  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); // Release old storage
  storage = b; // Point to new memory
  quantity = newQuantity; // Adjust the size
} ///:~

Listado 9.9. C09/Stash4.cpp


Una vez más, el programa de prueba que verifica que todo funciona correctamente.

//: C09:Stash4Test.cpp
//{L} Stash4
#include "Stash4.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, 100);
  ifstream in("Stash4Test.cpp");
  assure(in, "Stash4Test.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 9.10. C09/Stash4Test.cpp


Este es el mismo programa de prueba que se usó antes, de modo que la salida debería ser básicamente la misma.

La clase Stack incluso hace mejor uso de inline's.

//: C09:Stack4.h
// With inlines
#ifndef STACK4_H
#define STACK4_H
#include "../require.h"

class Stack {
  struct Link {
    void* data;
    Link* next;
    Link(void* dat, Link* nxt): 
      data(dat), next(nxt) {}
  }* head;
public:
  Stack() : head(0) {}
  ~Stack() {
    require(head == 0, "Stack not empty");
  }
  void push(void* dat) {
    head = new Link(dat, head);
  }
  void* peek() const { 
    return head ? head->data : 0;
  }
  void* pop() {
    if(head == 0) return 0;
    void* result = head->data;
    Link* oldHead = head;
    head = head->next;
    delete oldHead;
    return result;
  }
};
#endif // STACK4_H ///:~

Listado 9.11. C09/Stack4.h


Note que el destructor Link, que se presentó (vacío) en la versión anterior de Stack, ha sido eliminado. En pop(), la expresión delete oldHead simplemente libera la memoria usada por Link (no destruye el objeto data apuntado por el Link).

La mayoría de las funciones inline quedan bastante bien obviamente, en especial para Link. Incluso pop() parece justificado, aunque siempre que haya sentencias condicionales o variables locales no está claro que las inlines sean beneficiosas. Aquí, la función es lo suficientemente pequeña así que es probable que no haga ningún daño.

Si todas sus funciones son inline, usar la librería se convierte en algo bastante simple porque el enlazado es innecesario, como puede ver en el ejemplo de prueba (fíjese en que no hay Stack4.cpp).

//: C09:Stack4Test.cpp
//{T} Stack4Test.cpp
#include "Stack4.h"
#include "../require.h"
#include <fstream>
#include <iostream>
#include <string>
using namespace std;

int main(int argc, char* argv[]) {
  requireArgs(argc, 1); // File name is argument
  ifstream in(argv[1]);
  assure(in, argv[1]);
  Stack textlines;
  string line;
  // Read file and store lines in the stack:
  while(getline(in, line))
    textlines.push(new string(line));
  // Pop the lines from the stack and print them:
  string* s;
  while((s = (string*)textlines.pop()) != 0) {
    cout << *s << endl;
    delete s; 
  }
} ///:~

Listado 9.12. C09/Stack4Test.cpp


La gente escribe a veces clases con todas sus funciones inline, así que la clase completa está en el fichero de cabecera (verá en este libro que yo mismo lo hago). Durante el desarrollo de un programa probablemente esto es inofensivo, aunque a veces puede hacer que las compilaciones sean más lentas. Cuando el programa se estabiliza un poco, probablemente querrá volver a hacer las funciones no-inline donde sea conveniente.