Stash with constructors and destructors
The examples from previous chapters have
obvious functions that map to constructors and destructors:
initialize( ) and cleanup( ). Here’s the
Stash header using constructors and destructors:
//: 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 ///:~
The only member function definitions that
are changed are initialize( ) and cleanup( ), which have
been replaced with a constructor and 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;
}
} ///:~
You can see that the require.h
functions are being used to watch for programmer errors, instead of
assert( ). The output of a failed assert( ) is not as
useful as that of the require.h functions (which will be shown later in
the book).
Because inflate( ) is
private, the only way a require( ) could fail is if one of the other
member functions accidentally passed an incorrect value to
inflate( ). If you are certain this can’t happen, you could
consider removing the require( ), but you might keep in mind that
until the class is stable, there’s always the possibility that new code
might be added to the class that could cause errors. The cost of the
require( ) is low (and could be automatically removed using the
preprocessor) and the value of code robustness is high.
Notice in the following test program how
the definitions for Stash objects appear right before they are needed,
and how the initialization appears as part of the definition, in the constructor
argument list:
//: 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;
} ///:~
Also notice how the
cleanup( ) calls have been eliminated, but the
destructors are still automatically
called when intStash and stringStash go
out of scope.
One thing to be aware of in the
Stash examples: I’m being very careful to use only built-in types;
that is, those without destructors. If you were to try to copy class objects
into the Stash, you’d run into all kinds of problems and it
wouldn’t work right. The Standard C++ Library can actually make correct
copies of objects into its containers, but this is a rather messy and
complicated process. In the following Stack example, you’ll see
that pointers are used to sidestep this issue, and in a later chapter the
Stash will be converted so that it uses
pointers.