Selected C++ Example #20
// Chapter 9 Example #2
// This C++ example illustrates the implementation of garbage
// collection in C++. Garbage collection in C++ typically
// revolves around a memory handler function that ''knows''
// which classes in the system are hoarding memory. The memory
// handler knows which classes are hoarding memory because the
// author of the application overloaded the new and delete
// functions for the class in question.
// In this example, we have a LinkedList class, which contains
// Node objects. The Node class has overloaded its standard
// allocator and deallocator (i.e., operators new and delete)
// to hoard recyclable nodes on a freelist (a class variable)
// The Node class has a free_garbage class method, which users
// of the class can call. In this example, our memory handler,
// called free_garbage, detects that memory has run out and
// tells the Node class to put the memory back.
#include <iostream.h>
#include <new.h>
#include <stdlib.h>
#include <string.h>
typedef int DATUM;
// The memory handler for the application.
void garbage_collect();
// Notice that the Node class is granting friendship to the
// LinkedList class. Granting friendship to a class or method
// is typically considered bad style since it weakens data
// hiding. In this example, it is allowed since the Node class
// is really an internal implementation detail of the LinkedList.
// To force the LinkedList class to use accessor methods for
// each access would overly convolute the code of the LinkedList
// with items such as ''head->set_next(head->get_next())''
// instead of the much more readable ''head = head->next''.
class Node {
DATUM data;
Node* next;
static Node* freelist;
friend class LinkedList;
public:
Node(DATUM&);
void* operator new(size_t);
void operator delete(void*);
static int free_garbage();
static void print_freelist();
};
// The definition of the class variable ''freelist'' attached
//to the Node class.
Node* Node::freelist = NULL;
// The overloaded new operator (the standard allocator) will
// first try to recycle a node before going to the heap and
// getting one the expensive way.
void*
Node::operator new(size_t sz)
{
Node* p;
// First try to get a node off the freelist.
if (freelist != NULL) {
p = freelist;
freelist = freelist->next;
}
else {
// If that doesn't work, get one the old-fashioned, expensive
// way by making a trip to the heap.
p = (Node*) new char[sz];
}
return(p);
}
// Be sure that ''delete 0'' works properly (a NOP),
// then add the node to the freelist for later recycling.
void
Node::operator delete(void* p)
{
if (p != NULL) {
((Node*) p) ->next = freelist;
freelist = (Node*) p;
}
}
// The constructor for Node simply initializes the two data
// members, regardless of whether or not it is a recycled node.
Node::Node(DATUM& new_data)
{
data = new_data;
next = NULL;
}
// This class method is used for diagnostics. It prints the
// current addresses out on the Node's freelist.
void
Node::print_freelist()
{
Node* temp = freelist;
cout << ''Freelist: '';
while (temp != NULL) {
cout << temp << '' '';
temp = temp->next;
}
cout << ''\n'';
}
// This class function will throw away the freelist of
// nodes if a garbage-collection handler function
// requests it. Since nodes have an overloaded delete
// operator, the global delete must be called. Note the use
// of the scope resolution operator. This function returns
// the number of bytes it put back on the heap.
int
Node::free_garbage()
{
Node* temp;
int counter = 0;
while (freelist != NULL) {
temp = freelist;
freelist = freelist->next;
// Must use global delete to prevent infinite
// recursion between deleting a node off of the
// freelist and the overloaded delete operator putting
// the node back on the freelist.
::delete temp;
counter++;
}
return(counter*sizeof(Node));
}
// The LinkedList class is a user of Nodes. Its constructor
// and destructor call the overloaded new and delete of the
// Node class. Use of LinkedList objects results in a Freelist
// of Node objects stashed away in the Node class.
class LinkedList {
Node* head;
int length;
public:
LinkedList();
LinkedList(DATUM);
~LinkedList();
int insert(DATUM);
int remove(DATUM);
void traverse() const;
};
LinkedList::LinkedList()
{
head = NULL;
length = 0;
}
LinkedList::LinkedList(DATUM first_item)
{
head = new Node(first_item);
length = 1;
}
LinkedList::~LinkedList()
{
Node* temp;
while (head != NULL) {
temp = head;
head = head->next;
delete temp;
}
}
int
LinkedList::insert(DATUM new_item)
{
Node* temp = head;
if (temp == NULL) {
head = new Node(new_item);
}
else {
while(temp->next != NULL) {
temp = temp->next;
}
temp->next = new Node(new_item);
}
return(++length);
}
int
LinkedList::remove(DATUM bad_item)
{
Node* temp = head;
if (temp == NULL) {
return(0);
}
if (head->data == bad_item) {
head = head->next;
length--;
delete temp;
}
else {
while (temp->next != NULL&&
temp->next->data != bad_item){
temp = temp->next;
}
if (temp->next == NULL) {
return(0);
}
else {
Node* p = temp->next;
temp->next = temp->next->next;
length--;
delete p;
}
} return(1);
}
void
LinkedList::traverse()const
{
Node* temp = head;
cout << ''('';
while (temp != NULL) {
cout << temp->data << '''';
temp = temp->next;
}
cout << '')\n'';
}
// The main function first installs our memory handler.
// It then works with a number of LinkedList objects, which
// results in a freelist of nodes. We attempt to allocate
// an unallocatable amount of memory. The new operator for
// the array of characters is called. It calls the equivalent
// of malloc, which fails. New detects the failure and checks if
// there is a memory handler. There is one installed
// (garbage_collect), so we execute it and try malloc again,
// hoping for success. The hope is that the garbage collector
// will free enough memory so that the second attempt by new
// to allocate the memory is successful. In this example, the
// second attempt fails. The memory handler detects that no
// additional space has been freed and thus exits from the
// application, preventing infinite attempts to free memory even
// when there is none to free.
void
main()
{
set_new_handler(garbage_collect);
LinkedList a;
LinkedList b(100);
a.traverse(); b.traverse();
a.insert(1); a.insert(2);
a.insert(3); a.insert(4);
a.insert(5); a.insert(6);
b.insert(200); b.insert(300);
b.insert(400); b.insert(500);
b.insert(600); b.insert(700);
a.traverse();b.traverse();
Node::print_freelist();
a.remove(1); b.remove(100);
a.traverse();b.traverse();
a.remove(6); b.remove(700);
a.traverse();b.traverse();
a.remove(4); b.remove(400);
a.traverse();b.traverse();
Node::print_freelist();
a.insert(99); a.insert(199);
b.insert(-45);b.insert(-44);
a.traverse(); b.traverse();
Node::print_freelist();
char* big_buf = NULL;
// This block is impossible to retrieve. New will fail and will
// call our handler, which tells the Node class to free
// its horded nodes. The call to new still fails, so we exit
// the application.
big_buf = new char[65530L];
strcpy(big_buf, ''Arthur Riel'');
cout << ''big_buf = '' << big_buf <<''.\n'';
}
// The garbage-collection routine is a cornerstone in this
// example. It possesses knowledge of what element of the
// application is hoarding memory. When the heap is out of
// space, this routine tells the hoarders to put their memory
// back on the heap. If this routine cannot find any memory to
// free, it bails out.
void
garbage_collect()
{
int space_freed;
// Get some space freed on the heap by telling the hoarding
// Node class to put its memory back.
space_freed = Node::free_garbage();
// If there's nothing to free, simply fail. Otherwise,
// return and let new try again.
if (!space_freed){
cerr << ''Fatal Memory Error!!!\n'';
exit(-1);
}
}
|