Common Programming Mistakes
Intro
This document aims to combine the experience of many of the top KDE
developers about Qt and KDE frameworks dos and don'ts. The way they
were usually passed on to the next generation was by letting the youngsters
make the mistakes and then yell at them in public. After reading this document
you should be a lot less fearful of having your code reviewed by one
of the more grumpy KDE developers :) We will go over things, which
are not necessarily "bugs" but which make the code, either slower or less
readable.
General C++
In this section we'll guide through some of the more dusty corners of C++
which either tend to be misused or which people simply get wrong.
Anonymous namespaces vs statics
If you have a method in a class that does not access any members and therefore
does not need an object to operate, make it static. If additionally it is a
private helper function that is not needed outside of the file, make it a
file-static function. That hides the symbol completely.
Symbols defined in a C++ anonymous namespace do not have internal linkage.
Anonymous namespaces only give an unique name for that translation unit and
that is it; they don't change the linkage of the symbol at all. Linkage isn't
changed on those because the second phase of two-phase name lookup ignores
functions with internal linkages. Also, entities with internal linkage cannot
be used as template arguments.
So for now instead of using anonymous namespaces use static if you don't want
a symbol to be exported.
NULL pointer issues
First and foremost: it's fine to delete a null pointer. So the constructs
like:
if (ptr)
delete ptr;
are simply redundant.
Second of all you'll see null pointers marked with three types: 0, 0L and
NULL. The argument against using NULL was that while C defines it as a 0
void pointer, C++ defines it to not be a 0 void pointer. All conforming
C++ implementations will define NULL correctly so it's really not a problem.
The argument for 0L was that it was handled correctly in variable argument
functions, while 0 wasn't. Nowadays that's also an artifact.
It's more a question of getting used to something. As far as the code in CVS
goes you'll see 0 used more commonly than NULL.
When you delete a pointer, make sure you also set it to 0. So the idiom is:
delete ptr;
ptr = 0;
Member variables
You'll encounter four major styles of marking member variables in KDE:
- m_variable - lowercase m, underscore and the name of the variable
starting with a lowercase letter
- mVariable - lowercase m and the name of variable starting with
a uppercase letter
- variable_ - variable name starting with a lowercase letter and
then an underscore
- _variable - underscore and the name of variable starting with
a lowercase letter. It's being often frowned upon.
Be careful: this notation is used in some codes for
function parameters instead.
As it often happens there's no one correct way of doing it, so remember
to always follow the syntax used by the application/library to which
you are committing.
Static variables
Try to limit the number of static variables used in your code, especially
when committing to a library. Construction and initialization of large
number of static variables really hurts the startup times.
Do not use class-static variables, especially not in libraries and
loadable modules (but I would even discourage it in applications).
Static objects lead to lots of problems (crashes) due to undefined order of construction/destruction.
Instead, use a static pointer, together with KStaticDeleter.
Forward Declarations
You will reduce compile times by forward declaring classes when possible
instead of including their respective headers.
For example:
#include <qwidget.h> //bad
#include <qstringlist.h> //bad
#include <qstring.h> //bad
class SomeInterface
{
public:
virtual void widgetAction( QWidget *widget ) =0;
virtual void stringAction( const QString& str ) =0;
virtual void stringListAction( const QStringList& strList ) =0;
};
The above should look as follows:
class QWidget; //good
class QStringList; //good
class QString; //good
class SomeInterface
{
//as above
};
Iterators
There's few issues at hand here, so in no particular order:
Program Design
In this section we'll go over some common problems related to the design of
Qt/KDE applications.
Delayed Initialization
Although the design of modern C++ applications can be very complex, one
recurring problem that is generally easy to fix is not using the technique
of
delayed initialization, as
described by KDE
developer Zack Rusin. First of all, let's look at the standard way of
initializing a KDE application:
int main( int argc, char **argv )
{
....
KApplication a;
KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
MainWindow *window = new MainWindow( args );
a.setMainWidget( window );
window->show();
return a.exec();
}
Notice that
window is created before the
a.exec() call
that starts the event loop. This implies that we want to avoid doing
anything non-trivial in the top-level constructor, since it runs before
we can even show the window.
The solution is simple: We need to delay the construction of anything
besides the GUI until after the event loop has started. Here's how the
example class MainWindow's constructor could look:
MainWindow::MainWindow()
{
initGUI();
QTimer::singleShot( 0, this, SLOT(initObject()) );
}
void MainWindow::initGUI()
{
/* Here's where you construct your widgets. Note that the widgets you
* construct here shouldn't require complex initialization either, or
* you've defeated the purpose.
* All you want to do is create your GUI objects and QObject::connect
* the appropriate signals to their slots.
*/
}
void MainWindow::initObject()
{
/* This slot will be called as soon as the event loop starts. Here's
* where you could put everything else that needs to be done, including
* restoring values, reading files, session restoring, etc. It will
* still take time, but at least your window will be on the screen,
* making your app look busy.
*/
}
Using this technique may not buy you any overall time, but it makes your
app seem quicker to the user who is starting it, and is more reassuring to
boot, as users don't have to click on an icon and then wonder if the app
is really going to start or not.
Data Structures
In this section we'll go over some our most common pet-peeves which affect
data structures very commonly seen in Qt/KDE applications.
QPtrList
QPtrList, as the name describes, is a list of pointers. QPtrList's are
not implicitly shared. That means that every time you have
constructs like :
class SomeClass
{
public:
QPtrList returnList() const;
private:
QPtrList m_list;
};
each invocation of the returnList method will copy the full QPtrList by
iterating over all of its elements and inserting them in the newly
created QPtrList.
So we recommend to use QValueList instead of QPtrList. You'll loose the ability
to auto delete the pointers on destruction of the list, but it's trivial to add
code which does that in the destructor of your class.
QCString
One of the most common mistakes is using QCString::length() method in a loop.
QCString::length() calls strlen on each invocation, so calling it in a loop,
as in
QCString someCString = ...;
for( int i = 0; i < someCString.length(); ++i ) {
//Do something
}
Will result in exactly strlen(someCString)+1 calls of strlen(someCString).
If you have to use QCString, remember to cache the result of QCString::length()
method if you're planning to use it in a loop.
QByteArray
Often in KDE code developers want to transform the QByteArray to a QCString.
The code which is often tried looks as follows:
void someFunc( const QByteArray& data )
{
QCString str( data ); //this is wrong!
}
The trick here is that QByteArray don't have to be null terminated. This
introduces a subtle problem of trying to create a non-null terminated string.
The correct code for constructing QCString's from QByteArray's is:
QCString str( data, data.length() + 1 );
QString
It is common to want to test to see if a QString is null. It is faster
to do this using the isNull() method than by comparing to the static
QString::null value.
if ( mystring.isNull() ) { // YES!
}
if ( mystring == QString:: null ) { // NO!
}
Another twist to this comes when you want to test for the empty string
rather that a null string - again Qt provides a method for the job. In many
cases this can be preferred to testing for null:
if ( mystring.isEmpty() ) { // YES!
}
if ( mystring == "" ) { // NO!
}
QString
If you're reading in a file it is faster to convert it from the local
encoding to Unicode (QString) in one go rather than line by line. This
means that methods like QIODevice::readAll() are often a good
solution, followed by a single QString instantiation.
For larger files consider reading a block of lines then performing the
conversion, that way you get the opportunity to update your GUI. This can
be accomplished either by using qApp->processEvents() or by reentering the
event loop normally and using a timer to read in the blocks in the
background.
QString
Pass QStrings as const QString&. Even though QString is implicitly shared it
is still more efficient (and safer) to pass const references as opposed to
objects by value. So the canonical signature of a method taking QString
arguments would be:
void myMethod( const QString & foo, const QString & bar );
Actually, pretty much everything should be passed by const reference if possible. :)
QString
While QString is the tool of choice for many string handling situations there is
one where it is particularly inefficient. If you are pushing about and working
on data in QCStrings or QByteArrays take care not to pass it through methods
which take QString parameters and then make QCStrings or QByteArrays from them
again. For example:
QCString myData;
QString myNewData = mangleData( myData );
QString mangleData( const QString data )
{
QCString str = data.latin1();
// mangle
return QString(str);
}
The expensive thing happening here is the conversion to QString which does
conversion to Unicode internally. That is unnecessary as the first thing
the method does is convert back to latin1(). So if you are sure that the
Unicode conversion is not needed, try to avoid inadvertently using QString
along the way. so the above example should read:
QCString myData;
QCString myNewData = mangleData( myData );
QCString mangleData( const QCString& data )
QDomElement
When parsing XML documents, one often needs to iterate over all the elements.
You may be tempted to use the following code for that:
for ( QDomElement e = baseElement.firstChild().toElement(); !e.isNull();
e = e.nextSibling().toElement() )
{
...
}
That is not correct though, the above loop will stop prematurely when it
encounters a QDomNode that is something other than an element.
A comment for example. The correct loop looks like:
for ( QDomNode n = baseElement.firstChild(); !n.isNull();
n = n.nextSibling() )
{
QDomElement e = n.toElement();
if (e.isNull()) continue;
...
}
QObject
If you ever need to delete a QObject derived class from within one
of its own methods,
don't ever delete it via the:
delete this;
idiom. This will sooner or later cause your application/library to
crash because a method on that object might be invoked from the Qt
event loop via slots/signals after you deleted it.
Always use
deleteLater();
method from QObject which tries to do the same thing as delete this
in a safer way.
[ Edit ]