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 sampleThe 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 exampleThe 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.