|
|
|
|
Stack with constructors & destructors
Reimplementing the linked list
(inside Stack)
with constructors and destructors shows how neatly constructors and
destructors work with new and delete. Here’s the modified
header file:
//: C06:Stack3.h
// With constructors/destructors
#ifndef STACK3_H
#define STACK3_H
class Stack {
struct Link {
void* data;
Link* next;
Link(void* dat, Link* nxt);
~Link();
}* head;
public:
Stack();
~Stack();
void push(void* dat);
void* peek();
void* pop();
};
#endif // STACK3_H ///:~
Not only does Stack have a
constructor and destructor, but so does the nested class
struct Link:
//: C06:Stack3.cpp {O}
// Constructors/destructors
#include "Stack3.h"
#include "../require.h"
using namespace std;
Stack::Link::Link(void* dat, Link* nxt) {
data = dat;
next = nxt;
}
Stack::Link::~Link() { }
Stack::Stack() { head = 0; }
void Stack::push(void* dat) {
head = new Link(dat,head);
}
void* Stack::peek() {
require(head != 0, "Stack empty");
return head->data;
}
void* Stack::pop() {
if(head == 0) return 0;
void* result = head->data;
Link* oldHead = head;
head = head->next;
delete oldHead;
return result;
}
Stack::~Stack() {
require(head == 0, "Stack not empty");
} ///:~
The Link::Link( ) constructor
simply initializes the data and next pointers, so in
Stack::push( ) the line
head = new Link(dat,head);
not only allocates a new link (using
dynamic object creation with the keyword new, introduced in Chapter 4),
but it also neatly initializes the pointers for that link.
You may wonder why the destructor for
Link doesn’t do anything – in particular, why doesn’t
it delete the data pointer? There are two problems. In Chapter 4,
where the Stack was introduced, it was pointed out that you cannot
properly delete a void pointer if it points to an object (an
assertion that will be proven in Chapter 13). But in addition, if the
Link destructor deleted the data pointer, pop( ) would
end up returning a pointer to a deleted object, which would definitely be a bug.
This is sometimes referred to as the issue of
ownership: the Link and thus the
Stack only holds the pointers, but is not responsible for cleaning them
up. This means that you must be very careful that you know who is
responsible. For example, if you don’t pop( ) and
delete all the pointers on the Stack, they won’t get cleaned
up automatically by the Stack’s destructor. This can be a sticky
issue and leads to memory leaks,
so knowing who is responsible for cleaning up an object can make the difference
between a successful program and a buggy one – that’s why
Stack::~Stack( ) prints an error message if the Stack object
isn’t empty upon destruction.
Because the allocation and cleanup of the
Link objects are hidden within Stack – it’s part of
the underlying implementation – you don’t see it happening in the
test program, although you are responsible for deleting the pointers that
come back from pop( ):
//: C06:Stack3Test.cpp
//{L} Stack3
//{T} Stack3Test.cpp
// Constructors/destructors
#include "Stack3.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;
}
} ///:~
In this case, all the lines in
textlines are popped and deleted, but if they weren’t, you’d
get a require( ) message that would mean there was a memory
leak.
|
|
|