Thomas Zander
Qt
Posted by Thomas Zander
 in Qt
 on Thursday, October 09, 2008 @ 12:30

I’ve been in computers too long, whenever someone talks to me about error codes I think about the very old MacOs releases, or even longer ago the Amiga crash codes.
But I’m afraid you see them still, a dialog giving a very helpful “sorry, error 1234 happened”.

Now, anyone that is a coder knows how difficult it is to handle each and every error with a properly written error message. Preferrably one that actually makes sense to the user. And realistically speaking this tends not to happen. But before now the effect was typically to have useless messages like the above, or use some hacks that might get out of date on refactorings.
If you look at Qt there are several classes that have an error state; QNetworkReply is one of them. This error state is an enum and in C++ enums really are just integers.
In Qt we allow an enum to be more than just a set of integers using the Q_ENUMS macro. The direct benefit for you is that you can use introspection (/reflection) to get the string value of an enum.

So, no more excuses for just printing an integer error message, you can at least print the more readable enum value. Which means that technical people that don’t have the API docs for Qt handy can still figure out what went wrong.

Here is the code to do this;

QNetworkReply::NetworkError error;
error = fetchStuff();
if (error != QNetworkReply::NoError) {
    QString errorValue;
    QMetaObject meta = QNetworkReply::staticMetaObject;
    for (int i=0; i < meta.enumeratorCount(); ++i) {
        QMetaEnum m = meta.enumerator(i);
        if (m.name() == QLatin1String("NetworkError")) {
            errorValue = QLatin1String(m.valueToKey(error));
            break;
        }
    }
    QMessageBox box(QMessageBox::Information, "Failed to fetch",
                "Fetching stuff failed with error '%1`").arg(errorValue),
                QMessageBox::Ok);
    box.exec();
    return 1;
}

In Qt every class that has the Q_OBJECT macro will automatically have a static member “staticMetaObject” of the type QMetaObject. You can then find all sorts of cool things like the properties, signals, slots and indeed enums.

Happy hacking :)

16 Responses to “Coding tip; pretty-printing enum values.”

» Posted by Max Howell
 on Thursday, October 09, 2008 @ 14:06

Looks like a templated general purpose function is possible to me.

qErrorMessage( code );

Would be splendid.

» Posted by Max Howell
 on Thursday, October 09, 2008 @ 14:08

Your blog fails to escape < and >.

» Posted by Marc
 on Thursday, October 09, 2008 @ 15:43

Nice. Now, if you used exceptions… :)

» Posted by Andre
 on Thursday, October 09, 2008 @ 17:17

This might still get out of date on refactorings, e.g. if the metadata isnt called “NetworkError” anymore. Perhaps it could be more general by giving objects an error state in which case an error message meta data are attached.

» Posted by Matthias Ettrich
 on Thursday, October 09, 2008 @ 18:36
» Posted by mahoutsukai
 on Thursday, October 09, 2008 @ 19:54

“In Qt every class that has the Q_OBJECT macro will automatically have a static member “staticMetaObject” of the type QMetaObject.”

More precisely, every _derived_ class that has the Q_OBJECT macro (plus the classes Qt and QObject) will have staticMetaObject. moc aborts with an error if a base class has the Q_OBJECT macro. But for this case one can use the still undocumented (even though something else was promised at last year’s Akademy *sigh*) Q_GADGET macro. Unfortunately, there is a bug in moc which makes it impossible to have a base class with Q_GADGET macro but without enum and a derived class with Q_GADGET macro but with enum because moc will not generate the staticMetaObject for the base class which will cause a linker error (missing symbol BaseClass::staticMetaObject). (Yes, I have submitted a bug report for this a long time ago.)

The rather trivial patch to fix this bug in moc is:

Index: src/tools/moc/moc.cpp
===================================================================
— src/tools/moc/moc.cpp (revision 869687)
+++ src/tools/moc/moc.cpp (working copy)
@@ -683,7 +683,7 @@

next(RBRACE);

- if (!def.hasQObject && def.signalList.isEmpty() && def.slotList.isEmpty()
+ if (!def.hasQObject && !def.hasQGadget && def.signalList.isEmpty() && def.slotList.isEmpty()
&& def.propertyList.isEmpty() && def.enumDeclarations.isEmpty())
continue; // no meta object code required

Maybe it will be applied now. ;-)

» Posted by thomas
 on Thursday, October 09, 2008 @ 21:06

Mmh, I’d like to use something similar to sender() in my slotLogMessage(const QString &message) to prefix each message with its source. Is it somehow possible to get a string of the ORIGIN of a signal that was passed thorugh multiple classes? Like, the name of the class that first called emit?

» Posted by Cornelius Schumacher
 on Thursday, October 09, 2008 @ 21:08

Of course a simple pragmatic solution to the problem would be to return the error codes as a string instead of an integer in the first place.

» Posted by argonel
 on Friday, October 10, 2008 @ 02:02

too bad this can’t be done for QEvent::Type.

» Posted by Kevin Kofler
 on Friday, October 10, 2008 @ 05:46

This simple macro should rid you of the copy&paste:
#define ENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v)))

Use as follows:
QNetworkReply::NetworkError error;
error = fetchStuff();
if (error != QNetworkReply::NoError) {
QString errorValue = QLatin1String(ENUM_VALUE(QNetworkReply, NetworkError, error));
QMessageBox box(QMessageBox::Information, “Failed to fetch”,
“Fetching stuff failed with error ‘%1`”).arg(errorValue),
QMessageBox::Ok);
box.exec();
return 1;
}

» Posted by Kevin Kofler
 on Friday, October 10, 2008 @ 05:48

Oops, of course I mean QLatin1String(ENUM_NAME(QNetworkReply, NetworkError, error)); there. And it ate my indentation. :-(

» Posted by Ralf
 on Friday, October 10, 2008 @ 08:42

This was part of Simons presentation on “Secrets Of Qt” during the 2007 developer days. You can find the transcript here: http://labs.trolltech.com/page/Projects/DevDays/DevDays2007

\Ralf

» Posted by Michael "SPEED!" Howell
 on Friday, October 10, 2008 @ 16:07

@Cornelius: I can think of a few problems with that.
1) It would cause the string to be copied whenever the value is transfered between pieces of code. It’s faster to copy an integer.
2) It’s faster to compare an integer with an integer than a string with a string. Useful in error-handling code.
3) Enumerators get their own type, so you get more compile-time checking.

» Posted by Adam Higerd
 on Friday, October 10, 2008 @ 18:06

@Cornelius, Michael: Don’t forget the fact that human-visible strings could be localized!

» Posted by Felix
 on Friday, October 10, 2008 @ 22:54

This won’t help with displaying localized messages to the user, unless enum strings are marked for translation.

» Posted by Adam Higerd
 on Monday, October 13, 2008 @ 16:20

@Felix, et al: Marking the enum strings for translation is actually a really good idea; even if the enum strings themselves are in English you can use Linguist to fill in “localized” English “translations” of those strings for display purposes. (Similarly you can use Linguist to intelligently deal with plurals in the code’s “native” language. It’s a nice trick.)



© 2008 Nokia Corporation and/or its subsidiaries. Nokia, Qt and their respective logos are trademarks of Nokia Corporation in Finland and/or other countries worldwide.
All other trademarks are property of their respective owners.