|
|
|
|
Stack and Stash as templates
The recurring
“ownership” problems with the Stash and Stack
container classes that have been revisited throughout this book come from the
fact that these containers haven’t been able to know exactly what types
they hold. The nearest they’ve come is the Stack “container
of Object” that was seen at the end of Chapter 15 in
OStackTest.cpp.
If the client programmer doesn’t
explicitly remove all the pointers to objects that are held in the container,
then the container should be able to correctly delete those pointers. That is to
say, the container “owns” any objects that haven’t been
removed, and is thus responsible for cleaning them up. The snag has been that
cleanup requires knowing the type of the object, and creating a generic
container class requires not knowing the type of the object. With
templates, however, we can write code that doesn’t know the type of the
object, and easily instantiate a new version of that container for every type
that we want to contain. The individual instantiated containers do know
the type of objects they hold and can thus call the correct destructor
(assuming, in the typical case where polymorphism is involved, that a virtual
destructor has been provided).
For the Stack this turns out to be
quite simple since all of the member functions can be reasonably
inlined:
//: C16:TStack.h
// The Stack as a template
#ifndef TSTACK_H
#define TSTACK_H
template<class T>
class Stack {
struct Link {
T* data;
Link* next;
Link(T* dat, Link* nxt):
data(dat), next(nxt) {}
}* head;
public:
Stack() : head(0) {}
~Stack(){
while(head)
delete pop();
}
void push(T* dat) {
head = new Link(dat, head);
}
T* peek() const {
return head ? head->data : 0;
}
T* pop(){
if(head == 0) return 0;
T* result = head->data;
Link* oldHead = head;
head = head->next;
delete oldHead;
return result;
}
};
#endif // TSTACK_H ///:~
If you compare this to the
OStack.h example at the end of Chapter 15, you will see that Stack
is virtually identical, except that Object has been replaced with
T. The test program is also nearly identical, except that the necessity
for multiply-inheriting from string and Object (and even the need
for Object itself) has been eliminated. Now there is no MyString
class to announce its destruction, so a small new class is added to show a
Stack container cleaning up its objects:
//: C16:TStackTest.cpp
//{T} TStackTest.cpp
#include "TStack.h"
#include "../require.h"
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
class X {
public:
virtual ~X() { cout << "~X " << endl; }
};
int main(int argc, char* argv[]) {
requireArgs(argc, 1); // File name is argument
ifstream in(argv[1]);
assure(in, argv[1]);
Stack<string> textlines;
string line;
// Read file and store lines in the Stack:
while(getline(in, line))
textlines.push(new string(line));
// Pop some lines from the stack:
string* s;
for(int i = 0; i < 10; i++) {
if((s = (string*)textlines.pop())==0) break;
cout << *s << endl;
delete s;
} // The destructor deletes the other strings.
// Show that correct destruction happens:
Stack<X> xx;
for(int j = 0; j < 10; j++)
xx.push(new X);
} ///:~
The
destructor for X is
virtual, not because it’s necessary here, but because xx could
later be used to hold objects derived from X.
Notice how easy it is to create different
kinds of Stacks for string and for X. Because of the
template, you get the best of both worlds: the ease of use of the Stack
class along with proper cleanup.
|
|
|