Copyright (C) 1994 - 1997 by Zeev (Vladimir) Belkin
E-mail: [email protected]

A garbage collecting technique.

    Good C++ programming style assumes that a class object behavior should be so similar to a simple type object behavior as it possible. A program looks neat when it is possible to apply assignment and copy constructor to most of class objects. When a class object holds a reference to another object it is naturally to use the reference counting technique as a garbage collecting mechanism. It works well when the referenced objet is a C++ class object. The things are worse when the object should refer to OS driven object like a file or graphics device context. In this case it is necessary to have a particular class object which exclusive purpose is to hold the reference counter. In this case reference counting approach leads to serious runtime overhead. A garbage collecting technique, described in this article, was invented especially to collect OS driven object like mentioned above. Unlike the reference counting, this technique does not require any dynamically allocated auxiliary objects.

   This technique assumes that each the referencing object is an element of the bi-directional linked list that contains all objects that refer to the particular OS driven object. The referencing object's destructor can destroy the referenced OS driven object only when the linked list contains only one object - the object which destructor are executing.

   Three years ago I wrote a root class 'BiListGC' that can be inherited to add the GC functionality, described above, to the derived class. This class contains the implementation of the copy constructor and assignment operator. All you need to do in the derived class is to override the virtual function 'destroy_object' that should destroy the OS driven object and to call the method 'release_object' from the derived class destructor.

   The sample below shows how it is possible to use 'BiListGC'. The class 'File' holds a pointer to the standard C library driven 'FILE' object. This object is not OS driven directly, but it is similar to directly driven OS objects. You can apply the copy constructor or assignment to any instance of the 'File' class object.

// -- begin of the sample
class File: public BiListGC {
FILE *file;
virtual void destroy_object();
public:
operator FILE * () const { return file; } 

~File(); 

File(FILE *f=NULL);
};
void File::destroy_object() {
if (file!=NULL) fclose(file);
};
File::~File() { release_object(); };
File::File(FILE *f):file(f) {};
// -- end of the sample
   The sample below shows the use of the class 'File'.
// -- Begin of example.
// .. some #includes
void main() {
{File f1;
{File f2=fopen("my_file","w"), f3=f2;
f1=f3; 

fputs("line 1\n",f1); 

fputs("line 2\n",f2); 

fputs("line 3\n",f3);
} // objects 'f2' and 'f3' are destroyed, but 

  // the file still is opened. 

fputs("line 4\n",f1);
} // the file is closed.
} 

// -- the end of example
   The implementation of the class 'BiListGC' you can see below.
// -- begin of implementation
// declaration
class BiListGC {
BiListGC *prev, *next; 

void release_node();
protected:
virtual void destroy_object()=0; 

void release_object(); 

bool ready_to_destroy() const {
return (prev==NULL)&&(next==NULL);
}
public:
BiListGC(); 

~BiListGC(); 

void operator = (BiListGC &arg); 

BiListGC(BiListGC &arg);
};
// definitions
BiListGC::BiListGC():next(NULL),prev(NULL) {};
void BiListGC::release_node() {
if (prev!=NULL) prev->next=next; 

if (next!=NULL) next->prev=prev;
}
void BiListGC::release_object() {
if (ready_to_destroy()) destroy_object(); 

    else release_node();
}
BiListGC::~BiListGC() { release_node(); }
void BiListGC::operator = (BiListGC &arg) {
release_object(); 

if ((prev=arg.prev)!=NULL) prev->next=this; 

arg.prev=this; next=&arg;
}
BiListGC::BiListGC(BiListGC &arg) {
if ((prev=arg.prev)!=NULL) prev->next=this; 

arg.prev=this; next=&arg;
}
// -- end of implementation.
Main weakness of the described approach is that the technique is not thread safe.