Garbage Collection through the Back Door
Highly typed programming languages demand explicit (or at least implicit) creation of objects mentioning their types. They differ in how the objects are deleted: there are two approaches - direct deletion by the program or garbage collection by the language.
Explicitly deleting languages (eg. C++, Pascal) demand that the user knows when it is best to get rid of a dynamically created object. Garbage collected languages (eg. Java, SmallTalk) try to find out when an object is no longer used and automatically reclaim the memory.
Explicit deletion has the nice property that the programmer is in full control over the programs ressources - the amount of memory used and the amount and timing of CPU cycles spent on managing these ressources.
Garbage Collection on the other hand takes a lot of responsibility off the programmer by keeping track on object references inside the system and freeing memory as needed.
C++ is a language that demands that the user deletes objects explicitly. There are three important strategies on how to handle this:
The last strategy is called "memory leak" and usually regarded as a "bug".
The real problem with C++ is to find out which of the first two strategies is right and how to implement it. There are some cases in which the creating object is deleted much earlier than the child object and in which it is hard to find out which is the last object to handle it.
Qt maintains hierarchies of objects. For widgets (visible elements) these hierarchies represent the stacking order of the widgets themselves, for non-widgets they merely express "ownership" of objects. Qt deletes child objects together with their parent object. While this takes care of 90% of memory handling problems it also brings some challenges and leaves a few aspects open.
(QPointer docu)
QPointer is a template class that can watch a dynamic object and updates when the object is deleted, otherwise it behaves just like a normal pointer:
QDate *mydate=new QDate(QDate::currentDate()); QPointer<QDate> mypointer=mydata; mydate->year(); // -> 2005 mypointer->year(); // -> 2005
After deletion of the original object it starts to behave differently:
delete mydate; if(mydate==0) printf("clean pointer"); else printf("dangling pointer"); // -> "dangling pointer" if(mypointer.isNull()) printf("clean pointer"); else printf("dangling pointer"); // -> clean pointer
(QObjectCleanupHandler docu)
The cleanup handler of Qt is a further step towards automatic garbage collection. It can register several child objects and delete them all simultaniously when it itself is deleted. It also recognises automatically if any of its registered objects is deleted earlier and removes it from its list.
This class can be used any time when objects that are not in the same hierarchy need to be deleted upon a certain event (eg. several top level windows need to be closed when a button is pressed or when another window closes).
//instantiate handler QObjectCleanupHandler *cleaner=new QObjectCleanupHandler; //create some windows QPushButton *w; w=new QPushButton("Remove Me"); w->show(); //register first button cleaner->add(w); //if first button is pressed, it removes itself connect(w,SIGNAL(clicked()),w,SLOT(deleteLater())); //create second button with no function w=new QPushButton("Nothing"); cleaner->add(w); w->show(); //create third button to delete all w=new QPushButton("Remove All"); cleaner->add(w); connect(w,SIGNAL(clicked()),cleaner,SLOT(deleteLater())); w->show();
In the code above three windows containing a single button are created. If the first button ("Remove Me") is pressed, it will delete itself (via the "deleteLater" slot) and the cleanup handler will automatically remove it from its list. If the third button ("Remove All") is pressed it will delete the cleanup handler, which will in turn delete all buttons that are still open.
Sometimes objects are handed around to different other objects which all jointly own this object - so it is hard to determine when to delete this object. Fortunately with Qt it is possible to emulate garbage collection for child and parent classes that are derived from QObject.
There are several approaches to garbage collection. The easiest way is to implement instances counters, or one could store all owning objects. The garbage collection could be added to the class itself or handled by an outside object. Several of these methods will be developed below.
Instance counting is the easiest way of implementing garbage collection. For each reference made to the object the counter is increased, for each release it is decreased:
class CountedObject { public: CountedObject(){ctr=0;} void attach(){ctr++;} void detach(){ ctr--; if(ctr<=0)delete this; } private: int ctr; };
Each object that takes ownership of the object needs to call attach when it takes ownership and detach when it releases it. So far this approach does not take advantage of Qt - a more automatic approach would be this:
class CountedObject:public QObject { public: CountedObject(){ctr=0;} void attach(QObject*o){ ctr++; connect(o,SIGNAL(destroyed(QObject*)),this,SLOT(detach())); } public slots: void detach(){ ctr--; if(ctr<=0)delete this; } private: int ctr; };
In this code the detach() method will be called automatically when the owning object is deleted. Unfortunately this does not take into account that an owning object could call attach() twice by accident.
A more intelligent approach is to not only remember how many objects own this object, but which ones.
class CountedObject:public QObject { public: CountedObject(){} void attach(QObject*o){ //check owner for validity if(o==0)return; //check for duplicates if(owners.contains(o))return; //register owners.append(o); connect(o,SIGNAL(destroyed(QObject*)),this,SLOT(detach(QObject*))); } public slots: void detach(QObject*o){ //remove it owners.removeAll(o); //remove self after last one if(owners.size()==0)delete this; } private: QList<QObject*>owners; };
This code is finally able to protect itself against any faults caused by called attach or detach multiple times. Unfortunately it is not possible to protect the code against faults cause by not calling attach at all, since it is not possible to force C++ into telling the class how many pointers exist that point to any given object.
It is also possible to factor this code out into its own class, so that multiple objects can be handled by the same garbage collector instance and be delete simultaniously after the last parent was deleted.
qgarbagecollector.cpp
qgarbagecollector.h
The code linked above can care about multiple child objects in an independent garbage collector object. All children are deleted at the same time when either the garbage collector itself or the last parent (supervisor) is deleted. The garbage collector pointer does not need to be carried around, since the instance will delete itself upon completion of its task.