Silmor . de
Site Links:
Impressum / Publisher

Memory Management with Qt

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.

Default C++ Memory Handling

C++ is a language that demands that the user deletes objects explicitly. There are three important strategies on how to handle this:

  1. Let the creating object delete its child objects.
  2. Let the last object to handle an object delete it.
  3. Don't care about memory and forget about it.

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.

Default Qt Memory Handling

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

(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

(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.

Garbage Collection with Qt

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

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.

Owner Collection

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.

Generic Approach

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.


Webmaster: webmaster AT silmor DOT de