Wiki

Guarded Pointers in Qt 3 and Qt 4

by Mark Summerfield
Guarded pointers are pointers that are automatically set to 0 when the object they are pointing to is deleted. This means that it is always safe to ask "if (p)" with a guarded pointer. This article gives an example of the use of guarded pointers and highlights the differences between the Qt 3 and Qt 4 implementations.

In general, guarded pointers are best used when you need to store pointers to QObjects over time, especially when you don't own the object. Some programmers also make considerable use of guarded pointers as part of a "defensive programming" style of coding.

Example: Lazy Dialog Creation

Let's imagine that we have an application where the user can open a large number of modeless windows. One approach we could take is to create all the windows at startup but only show them when the user invokes them. This will make application startup slower, uses a lot of memory, and if many of the windows aren't actually used, the speed and memory costs will be paid unnecessarily every time the program is run.

Another approach is to just create and show the main window, and only create and show other windows when they're invoked. This saves on startup time and ensures that the application only uses the amount of memory actually needed. This approach has a few variations.

One variation is to simply hide windows that are closed and show them if they are invoked again. This maintains state between invocations (since the windows aren't destroyed), but prevents the program from reusing the memory of a window that's no longer used.

Another variation is to destroy windows that are closed---this is usually fine for small windows that are quick to construct, but for large complex dialogs (e.g., a Preferences dialog), the user may experience a short delay at every invocation. This variation has the benefit of minimizing memory use, but if state is to be maintained between invocations then the programmer must handle that explicitly themselves.

A third variation is to hide windows that are closed, and when they are hidden to begin a timer. When the timer times out, after say, five minutes, the window is then destroyed. If the window is shown before it has timed out, the timer is stopped. This variation means that if the user is invoking a window over and over again it remains in memory and its state is preserved so everything works quickly; but if the user stops using the window it is deleted after a while and the memory it used becomes accessible. In this latter case the programmer must still explicitly save/restore the window's state if they want to preserve it, when the window is created or destroyed.

If either the second or third variations are used it often proves convenient to have a member variable in the main window that holds a pointer to the windows the user could invoke. Without a guarded pointer we might use the following declarations:

FindDlg *findDlg;

And we'd write code similar to this:

void MainWindow::findDlgInvoked()
{
    if (!findDlg)
        createFindDlg();
    findDlg->show();
    findDlg->raise();
    findDlg->setActiveWindow();  // Qt 4: activateWindow()
}

Unfortunately the code above is fragile since we must ensure that findDlg is set to 0 whenever the dialog is destroyed. If we miss a case, the program will crash if the user invokes the dialog after the dialog is destroyed in the case we forgot.

If we use a guarded pointer we just need a single declaration:

QGuardedPtr<FindDlg> findDlg;

In Qt 4, the QGuardedPtr class has been renamed QPointer:

QPointer<FindDlg> findDlg;

Notice that we don't need to use a * in the declaration. The usage is identical:

void MainWindow::findDlgInvoked()
{
    if (!findDlg)
        createFindDlg();
    findDlg->show();
    findDlg->raise();
    findDlg->setActiveWindow();
}

This code allows us to defer creating the dialog until it is actually invoked by the user, and also works if we use destructive the Qt::WDestructiveClose flag (Qt::WA_DeleteOnClose in Qt 4) or a timer to destroy the dialog after it's been shown.

We don't have to remember to set findDlg to zero when the dialog is destroyed because the guarded pointer takes care of that for us.

Caveats and Overhead

Guarded pointers can generally be used just like standard pointers and both kinds can be mixed freely. If you have a function that takes a QWidget * you can pass it a QGuardedPtr<QWidget> or a QPointer<QWidget> just as if they were plain QWidget pointers. This means that there's no need to declare functions to take guarded pointer types.

There are only three caveats regarding Qt's guarded pointers. The first is that they can only point to QObjects: This includes all Qt's widgets and many more classes besides, so isn't a limitation in practice. The second is that they cannot be incremented or decremented, something that's normally only done for arrays anyway. The third is that they do not provide a solution for multithreaded programming: If you check a guarded pointer in one thread, "if (p)", and delete the object in another thread if you're unlucky with the scheduling the check will pass and then the other thread will get the processor and delete the object. So for multithreading, you still need a QMutex.

Some programmers were reluctant to use Qt 3's QGuardedPtr class because of its overhead. Every QGuardedPtr used one signal--slot connection (to connect to the target object's destroyed() signal), and one internal QObject to act as a receiver for the destroyed() signal. In the example we gave above, even if dozens or even hundreds of windows were involved, this overhead would be insignificant, but if the application was using QGuardedPtr for tens of thousands of QObjects the overhead would be considerable.

Qt 4's QPointer class provides the same functionality as Qt 3's QGuardedPtr, but with much less overhead. A QPointer still requires one signal--slot connection, but the only other overhead is a single pointer. Internally, Qt achieves this by using a special kind of signal--slot connection, where the target object is a QPointer instead of a QObject.


Copyright © 2005 Trolltech Trademarks