C++

KParts Ownership in One Minute

I embedded a viewer part, closed its window, and received a crash during shutdown. The viewer worked. My ownership did not.

A KPart is a QObject, and its widget is usually parented into the host’s widget tree. Those are related lifetimes, but they are not an invitation to delete everything in sight. I now keep the part pointer in the host, give the constructor the intended object parent, and let that relationship determine destruction.

The shape is roughly this:

KParts::ReadOnlyPart *part = factory->createPart(
    parentWidget, "viewer widget",
    this, "viewer part",
    "KParts::ReadOnlyPart");

setCentralWidget(part->widget());

The factory creates the component. The part owns its document behavior and supplies a widget for presentation. The shell owns the part through the QObject parent passed to the factory. I do not separately delete the widget; its parent chain handles that.

The other lifetime to remember is the component factory or library handle used to create the part. KDE’s loader machinery normally keeps the code available while objects from it exist. Bypassing that machinery and unloading a library while its C++ object remains is an efficient way to make a virtual call jump into empty space.

My preference is one obvious owner and no emergency deletes in destructors. If I cannot explain who owns the part without drawing several arrows, I fix that before debugging anything else. The crash at exit is rarely impressed by a beautiful toolbar.

What moc Actually Generates

I added a signal to a small Qt 3 class today, rebuilt, and received an undefined reference to the class’s virtual table. That message sounds like a C++ problem. In this case it meant I had forgotten about moc.

Qt’s signals and slots need information that the C++ compiler does not produce. A class using them declares Q_OBJECT:

class Counter : public QObject
{
    Q_OBJECT
public:
    Counter(QObject *parent = 0, const char *name = 0);

signals:
    void changed(int value);

public slots:
    void setValue(int value);
};

The Meta Object Compiler reads that declaration and writes ordinary C++ containing the meta-object table and dispatch code. The generated file supplies, among other things, the pieces behind className(), signal activation, and calls made through the string-based connect() mechanism.

In a KDE autotools project the build rules normally run moc for headers containing Q_OBJECT. If I put the class declaration in a .cpp file, I include its generated output at the bottom:

#include "counter.moc"

After adding or removing Q_OBJECT, regenerating the build files is sometimes necessary. A clean rebuild is also cheaper than interpreting every vtable error as an omen.

I like signals and slots because the sender does not need to know the receiver. The caveat is that the compiler cannot check the old SIGNAL() and SLOT() strings fully. A connection can compile and then complain at run time, so I watch the diagnostic output and keep signatures simple.

GCC 2.95 Is Part of the Platform

I lost an afternoon to code that compiled perfectly on my machine and failed on the machine that actually had to ship it. Mine had a newer compiler. The other one had GCC 2.95, as do quite a few systems on which KDE software is expected to build.

The useful lesson is not to argue with the compiler. It is to treat it as part of the target platform. If the project says it supports GCC 2.95, compile with GCC 2.95 before sending the change anywhere.

I now keep a separate build tree for this:

mkdir build-gcc295
cd build-gcc295
CC=gcc CXX=g++ ../configure --enable-debug
make

The compiler is particularly good at finding ambitious template code. Partial specialization, dependent names, and clever overloads can all expose old parser bugs or incomplete language support. Usually the least surprising spelling wins: name types explicitly, keep templates small, and avoid making overload resolution solve a riddle.

One more trap is mixing C++ libraries. An object compiled against an incompatible libstdc++ is not made safe merely because the linker accepted it. I build the whole program and its local libraries with the same toolchain.

I would rather write plain code than maintain compiler-specific branches. It is less exciting, but so is a build finishing successfully, and I have learned to appreciate that particular lack of excitement.