Development/Tutorials/Common Programming Mistakes (de): Difference between revisions

From KDE TechBase
(Some more translation)
No edit summary
 
(8 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{Template:I18n/Language Navigation Bar|Development/Tutorials/Common Programming Mistakes}}
 


{{TutorialBrowser (de)|
{{TutorialBrowser (de)|
Line 23: Line 23:


=== NULL Zeiger Probleme ===
=== NULL Zeiger Probleme ===
Als erstes und wichtigstes: Es ist OK einen Null Zeiger zu löschen. Daher sind Konstrukte, die einen Zeiger auf Null testen reduntant:
Als erstes und wichtigstes: Es ist OK einen Null Zeiger zu löschen. Daher sind Konstrukte, die einen Zeiger auf Null testen redundant:
<code cppqt>
<syntaxhighlight lang="cpp-qt">
if ( ptr ) {
if ( ptr ) {
   delete ptr;
   delete ptr;
}
}
</code>
</syntaxhighlight>


Beachten Sie jedoch, dass '''ein Null-Check erforderlich ''ist'', wenn Sie ein Array löschen''' - das liegt daran, dass ein relativ neuer Compiler auf Solaris-Systemen das anderweitig nicht richtig behandelt.  
Beachten Sie jedoch, dass '''ein Null-Check erforderlich ''ist'', wenn Sie ein Array löschen''' - das liegt daran, dass ein relativ neuer Compiler auf Solaris-Systemen das anderweitig nicht richtig behandelt.  
Line 34: Line 34:
Wenn Sie einen Zeiger löschen, stellen Sie sicher, dass sie ihn auf 0 setzen, so dass zukünftige Löschversuche nicht mit einer doppelten Löschung fehlschlagen. Der entsprechende Code dazu:
Wenn Sie einen Zeiger löschen, stellen Sie sicher, dass sie ihn auf 0 setzen, so dass zukünftige Löschversuche nicht mit einer doppelten Löschung fehlschlagen. Der entsprechende Code dazu:


<code cppqt>
<syntaxhighlight lang="cpp-qt">
delete ptr;  
delete ptr;  
ptr = 0;
ptr = 0;
</code>
</syntaxhighlight>
 
Ihnen mag aufgefallen sein, dass Null-Zeiger auf eine von drei Arten bezeichnet werden können: 0, 0L und NULL. In C ist NULL als null void Zeiger definiert. In C++ ist dies jedoch nicht möglich aufgrund einer strengeren Typprüfung. Daher definieren moderne C++ Implementationen eine "magische" Null-Zeiger Konstante, die einem beliebigen Zeiger zugewiesen werden kann. Ältere C++ Implementationen definierten es einfach als 0L oder 0, was keine zusätzliche Typsicherheit bietet - man könnte es einer Integervariable zuweisen, was offensichtlich falsch ist.


You may notice that null pointers are marked variously in one of three ways: 0, 0L and NULL. In C, NULL is defined as a null void pointer. However, in C++, this is not possible due to stricter type checking. Therefore, modern C++ implementations define it to a "magic" null pointer constant which can be assigned to any pointer. Older C++ implementations, OTOH, simply defined it to 0L or 0, which provides no additional type safety - one could assign it to an integer variable, which is obviously wrong.
Bei Zeigern bedeutet die Integerkonstante Null "Null-Zeiger" - unabhängig von der aktuellen Binärdarstellung eines Null-Zeigers. Daher ist die Entscheidung zwischen 0, 0L und NULL eine Frage des persönlichen Stils und der Gewohnheit statt technischer Gegebenheiten - was den Code in KDE's SVN betrifft, werden Sie mehr 0 als NULL verwendet sehen.


In pointer context, the integer constant zero means "null pointer" - irrespective of the actual binary representation of a null pointer. This means that the choice between 0, 0L and NULL is a question of personal style and getting used to something rather than a technical one - as far as the code in KDE's SVN goes you will see 0 used more commonly than NULL.
Beachten Sie jedoch, das wenn Sie eine Null-Zeiger Konstante an eine Funktion mit variabler Parameterliste übergeben, Sie diese explizit in eine Zeiger-Variable ändern müssen - der Compiler nimmt per Voreinstellung den Integerkontext an, was möglicherweise nicht der Binärdarstellung eines Null-Zeigers entspricht. Wieder ist es egal, ob Sie 0, 0L oder NULL verwenden, im Allgemeinen wird die kürzere Darstellung bevorzugt.
Note, however, that if you want to pass a null pointer constant to a function in a variable argument list, you *must* explicitly cast it to a pointer - the compiler assumes integer context by default, which might or might not match the binary representation of a pointer. Again, it does not matter whether you cast 0, 0L or NULL, but the shorter representation is generally preferred.


=== Member Variablen ===
=== Member Variablen ===
Line 57: Line 57:
=== Static variablen ===
=== Static variablen ===


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.
Versuchen Sie die Anzahl der static Variablen in Ihrem Code zu limitieren, besonders wenn Sie an einer Bibliothek arbeiten. Konstruktion und Initialisierung einer großen Anzahl von static Variablen kann die Startzeit stark verlängern.  
 
Do not use class-static variables, especially not in libraries and loadable modules though it is even discouraged in applications. Static objects lead to lots of problems such as hard to debug crashes due to undefined order of construction/destruction.


Instead, use a static pointer, together with <tt>K_GLOBAL_STATIC</tt> which is defined in <tt>kglobal.h</tt> and is used like this:
Benutzen Sie keine class-static Variablen, besonders nicht in Bibliotheken und ladbaren Modulen obwohl es sogar in Applikationen nicht empfohlen wird. Static Objekte führen zu einer Menge Problemen wie schwer zu verolgende Abstürze wegen einer undefinierten Reihenfolge des Konstruktion/Destruktion.


<code cppqt>
Benutzen Sie stattdessen einen static Zeiger, zusammen mit <tt>K_GLOBAL_STATIC</tt> welches in <tt>kglobal.h</tt> definiert wird und wie folgt benutzt wird:
<syntaxhighlight lang="cpp-qt">
class A { ... };
class A { ... };


Line 87: Line 86:
     qAddPostRoutine(globalA.destroy);
     qAddPostRoutine(globalA.destroy);
}
}
</code>
</syntaxhighlight>


See the [http://www.englishbreakfastnetwork.org/apidocs/apidox-kde-4.0/kdelibs-apidocs/kdecore/html/kglobal_8h.html#75ca0c60b03dc5e4f9427263bf4043c7 API documentation] for <tt>K_GLOBAL_STATIC</tt> for more information.
Sehen Sie auch [http://www.englishbreakfastnetwork.org/apidocs/apidox-kde-4.0/kdelibs-apidocs/kdecore/html/kglobal_8h.html#75ca0c60b03dc5e4f9427263bf4043c7 API Dokumentation] für <tt>K_GLOBAL_STATIC</tt>, um weitere Informationen zu erhalten.


=== Forward Deklarationen ===
=== Forward Deklarationen ===
Line 95: Line 94:
Die Übersetzungszeit kann reduziert werden, indem man Klassen ''forward'' deklariert anstatt den entsprechenden Header einzubinden. Ein Beispiel:
Die Übersetzungszeit kann reduziert werden, indem man Klassen ''forward'' deklariert anstatt den entsprechenden Header einzubinden. Ein Beispiel:


<code cppqt>
<syntaxhighlight lang="cpp-qt">
#include <QWidget>    // langsam
#include <QWidget>    // langsam
#include <QStringList> // langsam
#include <QStringList> // langsam
Line 106: Line 105:
     virtual void stringListAction( const QStringList& strList ) =0;
     virtual void stringListAction( const QStringList& strList ) =0;
};
};
</code>   
</syntaxhighlight>   
    
    
Das obige sollte statt dessen folgendermaßen formuliert werden:
Das obige sollte statt dessen folgendermaßen formuliert werden:


<code cppqt>
<syntaxhighlight lang="cpp-qt">
class QWidget;    // schnell
class QWidget;    // schnell
class QStringList; // schnell
class QStringList; // schnell
Line 121: Line 120:
     virtual void stringListAction( const QStringList& strList ) =0;
     virtual void stringListAction( const QStringList& strList ) =0;
};
};
</code>
</syntaxhighlight>


=== Iteratoren ===
=== Iteratoren ===
Bevorzugen Sie <tt>const_iterators</tt> vor normalen Iterationen wann immer es möglich ist.
{{improve (de)|Einige Abschnitte in diesem Kapitel konnte ich leider nicht übersetzen.}}
Containers, which are being implicitly shared often detach when a call to a non-const <tt>begin()</tt> or <tt>end()</tt> methods is made ({{qt|List}} ist ein Beispiel eines solchen Containers).
Wenn Sie einen const_iterator benutzen, achten Sie auch darauf, wirklich die const-Version von <tt>begin()</tt> und <tt>end()</tt> aufzurufen.


Prefer to use <tt>const_iterators</tt> over normal iterators when possible. Containers, which are being implicitly shared often detach when a call to a non-const <tt>begin()</tt> or <tt>end()</tt> methods is made ({{qt|List}} is an example of such a container). When using a const_iterator also watch out that you are really calling the const version of <tt>begin()</tt> and <tt>end()</tt>. Unless your container is actually const itself this probably will not be the case, possibly causing an unnecessary detach of your container. So basically whenever you use const_iterator initialize them using <tt>constBegin()</tt>/<tt>constEnd()</tt> instead, to be on the safe side.  
Unless your container is actually const itself this probably will not be the case, possibly causing an unnecessary detach of your container. So basically whenever you use const_iterator initialize them using <tt>constBegin()</tt>/<tt>constEnd()</tt> instead, to be on the safe side.  


Cache the return of the <tt>end()</tt> method call before doing iteration over large containers. For example:
Speichern Sie den Rückgabewert des <tt>end()</tt> Methodenaufrufs zwischen bevor Sie eine Iteration in großen Containern ausführen. Zum Beispiel:


<code cppqt>
<syntaxhighlight lang="cpp-qt">
QValueList<SomeClass> container;
QValueList<SomeClass> container;


//code which inserts a large number of elements to the container
//Code, der eine große Anzahl von Elementen in den Container einfügt.


QValueListConstIterator end( container.end() );
QValueListConstIterator end( container.end() );
Line 139: Line 145:
     itr != end; ++itr ) {
     itr != end; ++itr ) {
}
}
</code>
</syntaxhighlight>


This avoids the unnecessary creation of the temporary <tt>end()</tt> return object on each loop iteration, largely speeding it up.
Dadruch wird die unnötige Erzeugung eines temporären Rückgabe Objektes für <tt>end()</tt> in jedem Schleifendurchlauf vermieden und dadurch die Ausführung beschleunigt.


Prefer to use pre-increment over post-increment operators on iterators as this avoids creating an unnecessary temporary object in the process.
Benutzen Sie anstatt von Post-Increment Operatoren besser Pre-Increment Operatoren bei Iteratoren, da dies das Erzeugen von unnötigen, temporären Objekten in diesem Prozess vermeidet.


'''take care when erasing elements inside a loop'''
'''Seien Sie vorsichtig wenn Sie Elemente innerhalb einer Schleife löschen'''


When you want to erase some elements from the list, you maybe would use code similar to this:
Wenn Sie einige Elemente aus der Liste löschen wollen, könnten Sie vielleicht einen Code wie folgenden benutzen wollen:


<code cppqt>
<syntaxhighlight lang="cpp-qt">
QMap<int, Job *>::iterator it = m_activeTimers.begin();
QMap<int, Job *>::iterator it = m_activeTimers.begin();
QMap<int, Job *>::iterator itEnd = m_activeTimers.end();
QMap<int, Job *>::iterator itEnd = m_activeTimers.end();
Line 162: Line 168:
     }
     }
}
}
</code>
</syntaxhighlight>
 
Dieser Code wird eventuell wegen eines hängenden Iterators nach dem Aufruf von erase() einen Crash erzeugen.  
This code will potentially crash because it is a dangling iterator after the call to erase().
Sie müssen den Code folgendermaßen umschreiben:
You have to rewrite the code this way:
<syntaxhighlight lang="cpp-qt">
<code cppqt>
QMap<int, Job *>::iterator it = m_activeTimers.begin();
QMap<int, Job *>::iterator it = m_activeTimers.begin();
while (it != m_activeTimers.end())
while (it != m_activeTimers.end())
Line 179: Line 184:
     }
     }
}
}
</code>
</syntaxhighlight>
This problem is also discussed in the [http://doc.trolltech.com/4.3/qmap-iterator.html#details Qt documentation for QMap::iterator] but applies to '''all''' Qt iterators
Dieses Problem wird auch in [http://doc.trolltech.com/4.3/qmap-iterator.html#details Qt documentation for QMap::iterator] diskutiert, trifft aber auf alle Qt Iteratoren zu.


== Programm Design ==
== Programm Design ==
 
In diesem Abschnitt beschäftigen wir uns mit einigen allgemeinen Problemen im Zusammenhang mit den Design von Qt/KDE Applikationen.
In this section we will go over some common problems related to the design of Qt/KDE applications.


=== Verspätete Initialisierung ===
=== Verspätete Initialisierung ===
Obwohl das Design von modernen C++ Applikationen sehr komplex sein kann, ist ein immer wieder auftretendes Problem - was jedoch leicht zu beheben ist - die Tatsache nicht die Technik der [http://www.kdedevelopers.org/node/view/509 verspäteten Initialisierung] zu benutzen.


Although the design of modern C++ applications can be very complex, one recurring problem, which is generally easy to fix, is not using the technique of [http://www.kdedevelopers.org/node/view/509 delayed initialization].
Zunächst sehen wir uns den Standardweg einer Initialisierung einer KDE Applikation an:


First, let us look at the standard way of initializing a KDE application:
<syntaxhighlight lang="cpp-qt">
 
<code cppqt>
int main( int argc, char **argv )
int main( int argc, char **argv )
{
{
Line 207: Line 210:
     return a.exec();
     return a.exec();
}
}
</code>
</syntaxhighlight>
   
Beachten Sie, dass <tt>window</tt> vor dem Aufruf von <tt>a.exec()</tt>, der die Ereignisschleife startet, erzeugt wird. Das bedeutet, dass wir es vermeiden sollten, nicht-triviales im obersten Konstruktor durchzuführen, da dieser Läuft bevor das Fenster überhaupt dargestellt werden kann.
Notice that <tt>window</tt> is created before the <tt>a.exec()</tt> 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 is how the example class MainWindow's constructor could look to achieve this:
Die Lösung ist einfach: Wir müssen die Konsruktion von Dingen außer der GUI solange verzögern, bis die Ereignisschleife startet. Hier ist ein Beispiel, wie der Konstruktor eines MainWindows aussehen sollte, um das zu erreichen:


<code cppqt>
<syntaxhighlight lang="cpp-qt">
MainWindow::MainWindow()
MainWindow::MainWindow()
{
{
Line 222: Line 224:
void MainWindow::initGUI()
void MainWindow::initGUI()
{
{
     /* Construct your widgets here. Note that the widgets you
     /* Erzeugen Sie Ihre Widgets hier. Beachten Sie das die Widgets
     * construct here shouldn't require complex initialization
     * die Sie hier erzeugen, keine komplexe Initialisierung beötigen sollten
     * either, or you've defeated the purpose.
     * da ansonsten die Ganze Aktion nicht viel Sinn macht. Alles was
     * All you want to do is create your GUI objects and
     * hier geschehen sollte, ist die Erzeugung der GUI Objekte und das
     * QObject::connect
     * Verknüfen via QObject::connect der entsprechenden Signals mit deren 
     * the appropriate signals to their slots.
     * Slots
     */
     */
}
}
Line 233: Line 235:
void MainWindow::initObject()
void MainWindow::initObject()
{
{
     /* This slot will be called as soon as the event loop starts.
     /* Dieser Slot wird aufgrufen sobald die Ereignisschleife startet.  
     * Put everything else that needs to be done, including
     * Implementieren Sie hier alles was sonst noch getan werden muss,
     * restoring values, reading files, session restoring, etc here.
     * inklusive dem Wiederherstellen von Werten, dem Lesen von Dateien,  
     * It will still take time, but at least your window will be
    * Wiederherstellung von Sitzungen, etc.  
     * on the screen, making your app look active.
     * Das wird immer noch eine Zeit dauern, aber wenigstens ist Ihr
     * Fenster in dieser Zeit sichtbar und so macht die Applikation
    * einen aktiven Eindruck.
     */
     */
}
}
</code>
</syntaxhighlight>
 
Diese Technik gibt ihnen keinen Zeitvorteil (es läuft nichts schneller dadurch), aber die Applikation macht für den Benutzer den Eindruck, als würde sie schneller starten. Das wird dadurch bewerkstelligt, dass der Benutzer eine schnelle Antwort auf das starten der Applikation erhält.
    
    
Using this technique may not buy you any overall time, but it makes your app ''seem'' quicker to the user who is starting it. This increased perceived responsiveness is reassuring for the user as they get quick feedback that the action of launching the app has succeeded.
Wenn (und nur dann) der Start nicht in einem vernünftigen Zeitrahmen erfolgen kann, überlegen Sie sich, ein {{class|KSplashScreen}} einzusetzen.
 
When (and only when) the start up can not be made reasonably fast enough, consider using a {{class|KSplashScreen}}.


== Daten Strukturen ==
== Daten Strukturen ==
Line 252: Line 256:
=== non-POD typen übergeben ===
=== non-POD typen übergeben ===


Non-POD ("plain old data") types should be passed by const reference if at all possible. This includes anything other than the basic types such as <tt>char</tt> and <tt>int</tt>.
Nicht-POD ("plain old data") Typen sollten als const Referenz übergeben werden, wann immer das möglich ist. Das beinhaltet alle Datentypen außer <tt>char</tt> und <tt>int</tt>.


Take, for instance, {{qt|QString}}. They should always be passed into methods as <tt>const {{qt|QString}}&</tt>. Even though {{qt|QString}} is implicitly shared it is still more efficient (and safer) to pass const references as opposed to objects by value.  
Nehemen Sie zum Beispiel {{qt|QString}}. Diese sollten als Parameter an Methoden immer als <tt>const {{qt|QString}}&</tt> übergeben werden. Auch wenn {{qt|QString}} implicitly shared ist, ist das immer noch effizienter (und sicherer) es als const Referenz zu übergeben statt als Wert.


So the canonical signature of a method taking QString arguments is:
So allgemein sollte eine Methode, die QString als Argument nimmt, so aussehen:


<code cppqt>
<syntaxhighlight lang="cpp-qt">
void myMethod( const QString & foo, const QString & bar );
void myMethod( const QString & foo, const QString & bar );
</code>
</syntaxhighlight>


=== QObject ===
=== QObject ===
Sollten Sie jemals eine von QObject abgeleitete Klasse aus den eigenen Methoden heraus löschen sollen, löschen Sie es ''nicht'' folgendermaßen:
Sollten Sie jemals eine von QObject abgeleitete Klasse aus den eigenen Methoden heraus löschen sollen, löschen Sie es ''nicht'' folgendermaßen:
<code cppqt>
<syntaxhighlight lang="cpp-qt">
   delete this;
   delete this;
</code>
</syntaxhighlight>


Das wird früher oder später in einen Absturz führen, da eine Methode dieses Objektes aus der Qt-Ereignisschleife heraus via slots/signals aufgerufen werden könnte, nachdem Sie es gelöscht haben.
Das wird früher oder später in einen Absturz führen, da eine Methode dieses Objektes aus der Qt-Ereignisschleife heraus via slots/signals aufgerufen werden könnte, nachdem Sie es gelöscht haben.
Line 276: Line 280:




<code cppqt>
<syntaxhighlight lang="cpp-qt">
// Richtig
// Richtig
if ( mystring.isEmpty() ) {
if ( mystring.isEmpty() ) {
Line 288: Line 292:
if ( mystring == "" ) {
if ( mystring == "" ) {
}
}
</code>
</syntaxhighlight>


Obwohl es ein Unterschied zwischen "null" {{qt|QString}}s und leeren gibt, ist das nur ein historisches Artefakt, neuer Code sollte das nicht mehr benutzen.
Obwohl es ein Unterschied zwischen "null" {{qt|QString}}s und leeren gibt, ist das nur ein historisches Artefakt, neuer Code sollte das nicht mehr benutzen.


=== QString und das Lesen aus Dateien ===
=== QString und das Lesen aus Dateien ===
Wenn Sie aus einer Datei lesen, ist es schneller, in einem Rutsch von der lokalen Verschlüsselung nach Unicode ({{qt|QString}}) zu konvertieren als zeilenweise. Das bedeutet, dass Methoden wie <tt>{{qt|QIODevice}}::readAll()</tt> meist eine gute Lösung sind, gefolgt von einer einizigen {{qt|QString}} instantierung.


If you are reading in a file, it is faster to convert it from the local encoding to Unicode ({{qt|QString}}) in one go, rather than line by line. This means that methods like <tt>{{qt|QIODevice}}::readAll()</tt> are often a good solution, followed by a single {{qt|QString}} instantiation.
Bei größeren Dateien sollte sich in Betracht ziehen, einen Block von Zeilen zu lesen und dann zu konvertieren. Auf diese Art und Weise haben Sie die Möglichkeit ihre GUI zu aktualisieren. Das kann bewekstelligt werden, indem Sie wieder in die Ereignisschleife eintreten und gleichteitig mittels eines Timers die Blöcke im Hintergrund lesen oder indem Sie eine lokale Ereignisschleife definieren.  


For larger files, consider reading a block of lines and then performing the conversion. That way you get the opportunity to update your GUI. This can be accomplished by reentering the event loop normally, along with using a timer to read in the blocks in the background, or by creating a local event loop.  
Man könnte auch <tt>qApp->processEvents()</tt> aufrufen, davon wird jedoch abgetraten, da es leicht zu zum Teil fatalen Problemen führt.  


While one can also use <tt>qApp->processEvents()</tt>, it is discouraged as it easily leads to subtle yet often fatal problems.
=== QString aus einem KProcess lesen ===
{{class|KProcess}} löst das Signal <tt>readyReadStandard{Output|Error}</tt> aus, sobald Daten eintreffen.


=== QString aus einem KProcess lesen ===
Ein häufiger Fehler ist es, alle verfügbaren Daten im verbundenen Slot zu lesen und sofort in ein {{qt|QString}} zu konvertieren: Die Daten könnten in kleineren unregelmäßigen Portionen einreffen, daher könnten multi-byte Zeichen in Stücke geschnitten und daher ungültig werden. Mehrere Ansätze existieren:


{{class|KProcess}} emits the signals <tt>readyReadStandard{Output|Error}</tt> as data comes in.
A common mistake is reading all available data in the connected slot and converting it to {{qt|QString}} right away: the data comes in arbitrarily segmented chunks, so multi-byte characters might be cut into pieces and thus invalidated. Several approaches to this problem exist:
<ul>
<ul>
<li>Do you really need to process the data as it comes in? If not, just use <tt>readAllStandard{Output|Error}</tt> after the process has exited. Unlike in KDE3, KProcess is now able to accumulate the data for you.</li>
<li>Müssen Sie die Daten wirklich sofort verarbeiten sobald sie eintreffen? Wenn nicht, benutzen Sie lieber <tt>readAllStandard{Output|Error}</tt> nachdem der Prozess beendet wurde. Anders als in KDE3 ist KProcess nun in der Lage, die Daten zu sammeln.</li>
<li>Accumulate data chunks in the slots and process them each time a newline arrives or after some timeout passes. [http://websvn.kde.org/branches/KDE/3.5/kdevelop/lib/widgets/processlinemaker.cpp?view=markup&rev=737977 Example code] (KDE3 based)</li>
<li>Sammeln Sie die Datenbrocken im Slot und bearbeiten Sie sie immer dann, wenn ein Newline kommt oder ein bestimmter Timeout auftritt.[http://websvn.kde.org/branches/KDE/3.5/kdevelop/lib/widgets/processlinemaker.cpp?view=markup&rev=737977 Example code] (KDE3 based)</li>
<li>Wrap the process into a {{qt|QTextStream}} and read line-wise. While this should work in theory, it does '''not''' work in practice currently.</li>
<li>Fügen Sie den Prozess in ein {{qt|QTextStream}} ein und lesen Sie diesen zeilenweise. Während das zumindest theoretisch funktionieren sollte, funktioniert das in der Praxis derzeit '''nicht'''.</li>
</ul>
</ul>


=== QString und QByteArray ===
=== QString und QByteArray ===
 
Während {{qt|QString}} das Werkzeug der Wahl für viele Situationen ist, in denen mit Zeichenketten gearbeitet wird, gibt es eine Situation, wo dies äußerst ineffizient ist. Wenn Sie mit Daten arbeitet, die in einem {{qt|QByteArray}} gespeichert sind, sehen Sie sich vor, dass Sie diese nicht an Methoden übergeben, die einen {{qt|QString}} Parameter benötigen und daraus wieder ein QByteArray macht.
While {{qt|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 {{qt|QByteArray}}s, take care not to pass it through methods which take {{qt|QString}} parameters; then make QByteArrays from them again.


For example:  
For example:  


<code cppqt>
<syntaxhighlight lang="cpp-qt">
QByteArray myData;
QByteArray myData;
QString myNewData = mangleData( myData );
QString myNewData = mangleData( myData );
Line 325: Line 328:
     return QString(str);
     return QString(str);
}
}
</code>
</syntaxhighlight>
   
 
The expensive thing happening here is the conversion to {{qt|QString}}, which does a conversion to Unicode internally. This is unnecessary because, the first thing the method does is convert it back using <tt>toLatin1()</tt>. So if you are sure that the Unicode conversion is not needed, try to avoid inadvertently using QString along the way.  
Der kostspielige Teil hier ist die Konvertierung nach {{qt|QString}}, was intern als Konvertierung in Unicode durchgeführt wird. Das ist unnötig, da das erste, was die Methode tut, eine Rückkonvertierung nach <tt>toLatin1()</tt> ist. Wenn Sie sich also sicher sind, dass eine Unicode Konvertierung nicht bennötigt wird, versuchen sich Zwischenschritte mit QString zu vermeiden.


Das obige Beispiel sollte statt dessen geschrieben werden als:
Das obige Beispiel sollte statt dessen geschrieben werden als:


<code cppqt>
<syntaxhighlight lang="cpp-qt">
QByteArray myData;
QByteArray myData;
QByteArray myNewData = mangleData( myData );
QByteArray myNewData = mangleData( myData );


QByteArray mangleData( const QByteArray& data )
QByteArray mangleData( const QByteArray& data )
</code>
</syntaxhighlight>


=== QDomElement ===
=== QDomElement ===
When ein XML Dokument geparst wird, benötigt man häufig eine Iteration über alle Elemente. Man könnte versucht sein, folgenden Code dafür zu benutzen:
When ein XML Dokument geparst wird, benötigt man häufig eine Iteration über alle Elemente. Man könnte versucht sein, folgenden Code dafür zu benutzen:


<code cppqt>
<syntaxhighlight lang="cpp-qt">
for ( QDomElement e = baseElement.firstChild().toElement();
for ( QDomElement e = baseElement.firstChild().toElement();
       !e.isNull();
       !e.isNull();
Line 347: Line 350:
       ...
       ...
}
}
</code>
</syntaxhighlight>


Das ist jedoch nicht korrekt: Die obige Schleife wird vorzeitig beendet wenn es auf einen {{qt|QDomNode}} trifft, der etwas anderes als ein Element ist (zum Beispiel ein Kommentar).
Das ist jedoch nicht korrekt: Die obige Schleife wird vorzeitig beendet wenn es auf einen {{qt|QDomNode}} trifft, der etwas anderes als ein Element ist (zum Beispiel ein Kommentar).
Line 353: Line 356:
Die korrekte Schleife sieht so aus:
Die korrekte Schleife sieht so aus:


<code cppqt>
<syntaxhighlight lang="cpp-qt">
for ( QDomNode n = baseElement.firstChild(); !n.isNull();
for ( QDomNode n = baseElement.firstChild(); !n.isNull();
       n = n.nextSibling() ) {
       n = n.nextSibling() ) {
Line 362: Line 365:
     ...
     ...
}
}
</code>
</syntaxhighlight>

Latest revision as of 15:28, 14 July 2012


Häufige Programmierfehler
Anleitungsserie   Grundlagen
Voriges Kapitel   None
Nächstes Kapitel   n/a
Weiterführende Texte   n/a
Navigation   Deutsche Startseite

Zusammenfassung

Diese Anleitung gibt ein paar Tips aus dem Erfahrungsschatz von KDE Entwicklern bezüglich was man in Qt und KDE machen und was lieber sein lassen sollte. Neben aktuellen Fehler behandelt es auch Dinge, die nicht unbedingt als Fehler gelten jedoch den Code langsamer oder weniger lesbar machen.

C++ allgemein

Dieses Kapitel beschäftigt sich mit einigen dunklen Ecken von C++, die gerne falsch benutzt oder schlicht und einfach falsch verstanden werden.

Anonyme Namensräume vs. statics

Wenn Sie eine Methode in einer Klasse haben, die nicht auf Members zugreift und daher kein Objekt zu seiner Arbeit benötigt, machen Sie es static. Wenn diese Methode dann zusätzlich nur eine private Hilfsfunktion ist, welche außerhalb dieser Datei nicht benötigt wird, machen Sie daraus eine file-static Funktion, dadurch wird das Symbol komplett verborgen.

Symbole die in C++ in einem anonymen Namensraum definiert werden, haben keine interne Verknüpfung. Anonyme Namensräume vergeben nur einen eindeutigen Namen für diese Übersetzungseinheit und das ist alles. Die Verknüpfung des Symbols wird überhaupt nicht verändert, weil die zweite Phase einer zwei-Phasen Namenssuche Funktionen mit interner Verknüfung ignoriert. Weiterhin können Elemente mit interner Verknüpfung nicht als Argumente für Templates benutzt werden.

Wenn Sie also nicht wollen, dass ein Symbol exportiert wird, benutzen Sie statt anonymen Namensräumen statische.

NULL Zeiger Probleme

Als erstes und wichtigstes: Es ist OK einen Null Zeiger zu löschen. Daher sind Konstrukte, die einen Zeiger auf Null testen redundant:

if ( ptr ) {
   delete ptr;
}

Beachten Sie jedoch, dass ein Null-Check erforderlich ist, wenn Sie ein Array löschen - das liegt daran, dass ein relativ neuer Compiler auf Solaris-Systemen das anderweitig nicht richtig behandelt.

Wenn Sie einen Zeiger löschen, stellen Sie sicher, dass sie ihn auf 0 setzen, so dass zukünftige Löschversuche nicht mit einer doppelten Löschung fehlschlagen. Der entsprechende Code dazu:

delete ptr; 
ptr = 0;

Ihnen mag aufgefallen sein, dass Null-Zeiger auf eine von drei Arten bezeichnet werden können: 0, 0L und NULL. In C ist NULL als null void Zeiger definiert. In C++ ist dies jedoch nicht möglich aufgrund einer strengeren Typprüfung. Daher definieren moderne C++ Implementationen eine "magische" Null-Zeiger Konstante, die einem beliebigen Zeiger zugewiesen werden kann. Ältere C++ Implementationen definierten es einfach als 0L oder 0, was keine zusätzliche Typsicherheit bietet - man könnte es einer Integervariable zuweisen, was offensichtlich falsch ist.

Bei Zeigern bedeutet die Integerkonstante Null "Null-Zeiger" - unabhängig von der aktuellen Binärdarstellung eines Null-Zeigers. Daher ist die Entscheidung zwischen 0, 0L und NULL eine Frage des persönlichen Stils und der Gewohnheit statt technischer Gegebenheiten - was den Code in KDE's SVN betrifft, werden Sie mehr 0 als NULL verwendet sehen.

Beachten Sie jedoch, das wenn Sie eine Null-Zeiger Konstante an eine Funktion mit variabler Parameterliste übergeben, Sie diese explizit in eine Zeiger-Variable ändern müssen - der Compiler nimmt per Voreinstellung den Integerkontext an, was möglicherweise nicht der Binärdarstellung eines Null-Zeigers entspricht. Wieder ist es egal, ob Sie 0, 0L oder NULL verwenden, im Allgemeinen wird die kürzere Darstellung bevorzugt.

Member Variablen

Sie werden vielleicht vier Hauptstile bemerkt haben, wie in KDE-Klassen Member-Variablen benannt werden:

  • m_variable kleines m, Unterstrich und der Name der Variable mit einem Kleinbuchstaben am Anfang. Das ist der am meisten verwendete Stil und wird im Code von kdelibs bevorzugt.
  • mVariable kleines m und der Name der Variable mit einem Großbuchstaben am Anfang
  • variable_ Name der Variable mit einem Kleinbuchstaben am Anfang und variable einem Unterstrich am Ende
  • _variable Unterstrich und der Name der Variable mit einem Kleinbuchstaben am Anfang. Dieser Stil wird nicht so gerne gesehen, da diese Notation auch in Code für Funktionsparameter verwendet wird.

Wie immer gibt es nicht nur einen korrekten Weg es zu machen, so folgen Sie einfach der Syntax der Applikation/Bibliothek zu welcher Sie Code ergänzen.

Static variablen

Versuchen Sie die Anzahl der static Variablen in Ihrem Code zu limitieren, besonders wenn Sie an einer Bibliothek arbeiten. Konstruktion und Initialisierung einer großen Anzahl von static Variablen kann die Startzeit stark verlängern.

Benutzen Sie keine class-static Variablen, besonders nicht in Bibliotheken und ladbaren Modulen obwohl es sogar in Applikationen nicht empfohlen wird. Static Objekte führen zu einer Menge Problemen wie schwer zu verolgende Abstürze wegen einer undefinierten Reihenfolge des Konstruktion/Destruktion.

Benutzen Sie stattdessen einen static Zeiger, zusammen mit K_GLOBAL_STATIC welches in kglobal.h definiert wird und wie folgt benutzt wird:

class A { ... };

K_GLOBAL_STATIC(A, globalA)

void doSomething()
{
     A *a = globalA;
     ...
}

void doSomethingElse()
{
    if (globalA.isDestroyed()) {
        return;
    }
    A *a = globalA;
    ...
}

void installPostRoutine()
{
    qAddPostRoutine(globalA.destroy);
}

Sehen Sie auch API Dokumentation für K_GLOBAL_STATIC, um weitere Informationen zu erhalten.

Forward Deklarationen

Die Übersetzungszeit kann reduziert werden, indem man Klassen forward deklariert anstatt den entsprechenden Header einzubinden. Ein Beispiel:

#include <QWidget>     // langsam
#include <QStringList> // langsam
#include <QString>     // langsam
class SomeInterface
{
public:
    virtual void widgetAction( QWidget *widget ) =0;
    virtual void stringAction( const QString& str ) =0;
    virtual void stringListAction( const QStringList& strList ) =0;
};

Das obige sollte statt dessen folgendermaßen formuliert werden:

class QWidget;     // schnell
class QStringList; // schnell
class QString;     // schnell
class SomeInterface
{
public:
    virtual void widgetAction( QWidget *widget ) =0;
    virtual void stringAction( const QString& str ) =0;
    virtual void stringListAction( const QStringList& strList ) =0;
};

Iteratoren

Bevorzugen Sie const_iterators vor normalen Iterationen wann immer es möglich ist.

noframe
noframe

Dieser Abschnitt muss verbessert werden: Bitte hilf mit, verwirrende Abschnitte zu bereinigen und Abschnitte zu reparieren die ein todo beinhalten


Einige Abschnitte in diesem Kapitel konnte ich leider nicht übersetzen.
Warnung



Containers, which are being implicitly shared often detach when a call to a non-const begin() or end() methods is made (List ist ein Beispiel eines solchen Containers).

Wenn Sie einen const_iterator benutzen, achten Sie auch darauf, wirklich die const-Version von begin() und end() aufzurufen.

Unless your container is actually const itself this probably will not be the case, possibly causing an unnecessary detach of your container. So basically whenever you use const_iterator initialize them using constBegin()/constEnd() instead, to be on the safe side.

Speichern Sie den Rückgabewert des end() Methodenaufrufs zwischen bevor Sie eine Iteration in großen Containern ausführen. Zum Beispiel:

QValueList<SomeClass> container;

//Code, der eine große Anzahl von Elementen in den Container einfügt.

QValueListConstIterator end( container.end() );

for ( QValueListConstIterator itr( container.begin() );
     itr != end; ++itr ) {
}

Dadruch wird die unnötige Erzeugung eines temporären Rückgabe Objektes für end() in jedem Schleifendurchlauf vermieden und dadurch die Ausführung beschleunigt.

Benutzen Sie anstatt von Post-Increment Operatoren besser Pre-Increment Operatoren bei Iteratoren, da dies das Erzeugen von unnötigen, temporären Objekten in diesem Prozess vermeidet.

Seien Sie vorsichtig wenn Sie Elemente innerhalb einer Schleife löschen

Wenn Sie einige Elemente aus der Liste löschen wollen, könnten Sie vielleicht einen Code wie folgenden benutzen wollen:

QMap<int, Job *>::iterator it = m_activeTimers.begin();
QMap<int, Job *>::iterator itEnd = m_activeTimers.end();

for( ; it!=itEnd ; ++it )
{
    if(it.value() == job)
    {
        //A timer for this job has been found. Let's stop it.
        killTimer(it.key());
        m_activeTimers.erase(it);
    }
}

Dieser Code wird eventuell wegen eines hängenden Iterators nach dem Aufruf von erase() einen Crash erzeugen. Sie müssen den Code folgendermaßen umschreiben:

QMap<int, Job *>::iterator it = m_activeTimers.begin();
while (it != m_activeTimers.end())
{
    QMap<int, Job *>::iterator prev = it;
    ++it;
    if(prev.value() == job)
    {
        //A timer for this job has been found. Let's stop it.
        killTimer(prev.key());
        m_activeTimers.erase(prev);
    }
}

Dieses Problem wird auch in Qt documentation for QMap::iterator diskutiert, trifft aber auf alle Qt Iteratoren zu.

Programm Design

In diesem Abschnitt beschäftigen wir uns mit einigen allgemeinen Problemen im Zusammenhang mit den Design von Qt/KDE Applikationen.

Verspätete Initialisierung

Obwohl das Design von modernen C++ Applikationen sehr komplex sein kann, ist ein immer wieder auftretendes Problem - was jedoch leicht zu beheben ist - die Tatsache nicht die Technik der verspäteten Initialisierung zu benutzen.

Zunächst sehen wir uns den Standardweg einer Initialisierung einer KDE Applikation an:

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();
}

Beachten Sie, dass window vor dem Aufruf von a.exec(), der die Ereignisschleife startet, erzeugt wird. Das bedeutet, dass wir es vermeiden sollten, nicht-triviales im obersten Konstruktor durchzuführen, da dieser Läuft bevor das Fenster überhaupt dargestellt werden kann.

Die Lösung ist einfach: Wir müssen die Konsruktion von Dingen außer der GUI solange verzögern, bis die Ereignisschleife startet. Hier ist ein Beispiel, wie der Konstruktor eines MainWindows aussehen sollte, um das zu erreichen:

MainWindow::MainWindow()
{
    initGUI();
    QTimer::singleShot( 0, this, SLOT(initObject()) );
}

void MainWindow::initGUI()
{
    /* Erzeugen Sie Ihre Widgets hier. Beachten Sie das die Widgets
     * die Sie hier erzeugen, keine komplexe Initialisierung beötigen sollten
     * da ansonsten die Ganze Aktion nicht viel Sinn macht. Alles was
     * hier geschehen sollte, ist die Erzeugung der GUI Objekte und das
     * Verknüfen via QObject::connect der entsprechenden Signals mit deren  
     * Slots
     */
}

void MainWindow::initObject()
{
    /* Dieser Slot wird aufgrufen sobald die Ereignisschleife startet. 
     * Implementieren Sie hier alles was sonst noch getan werden muss,
     * inklusive dem Wiederherstellen von Werten, dem Lesen von Dateien, 
     * Wiederherstellung von Sitzungen, etc. 
     * Das wird immer noch eine Zeit dauern, aber wenigstens ist Ihr 
     * Fenster in dieser Zeit sichtbar und so macht die Applikation
     * einen aktiven Eindruck.
     */
}

Diese Technik gibt ihnen keinen Zeitvorteil (es läuft nichts schneller dadurch), aber die Applikation macht für den Benutzer den Eindruck, als würde sie schneller starten. Das wird dadurch bewerkstelligt, dass der Benutzer eine schnelle Antwort auf das starten der Applikation erhält.

Wenn (und nur dann) der Start nicht in einem vernünftigen Zeitrahmen erfolgen kann, überlegen Sie sich, ein KSplashScreen einzusetzen.

Daten Strukturen

In this section we will go over some of our most common pet-peeves which affect data structures very commonly seen in Qt/KDE applications.

non-POD typen übergeben

Nicht-POD ("plain old data") Typen sollten als const Referenz übergeben werden, wann immer das möglich ist. Das beinhaltet alle Datentypen außer char und int.

Nehemen Sie zum Beispiel QString. Diese sollten als Parameter an Methoden immer als const QString& übergeben werden. Auch wenn QString implicitly shared ist, ist das immer noch effizienter (und sicherer) es als const Referenz zu übergeben statt als Wert.

So allgemein sollte eine Methode, die QString als Argument nimmt, so aussehen:

void myMethod( const QString & foo, const QString & bar );

QObject

Sollten Sie jemals eine von QObject abgeleitete Klasse aus den eigenen Methoden heraus löschen sollen, löschen Sie es nicht folgendermaßen:

   delete this;

Das wird früher oder später in einen Absturz führen, da eine Methode dieses Objektes aus der Qt-Ereignisschleife heraus via slots/signals aufgerufen werden könnte, nachdem Sie es gelöscht haben.

Nutzen Sie statt dessen immer QObject::deleteLater(), was das gleiche wie delete this erledigt jedoch in einer sicheren Art und Weise.

Leere QStrings

Häufig muss geprüft werden, ob ein QString leer ist. Hier sind drei Ansätze das zu prüfen, wobei nur die ersten beiden korrekt sind:


// Richtig
if ( mystring.isEmpty() ) {
}

// Richtig
if ( mystring == QString() ) {
}

// Falsch! ""
if ( mystring == "" ) {
}

Obwohl es ein Unterschied zwischen "null" QStrings und leeren gibt, ist das nur ein historisches Artefakt, neuer Code sollte das nicht mehr benutzen.

QString und das Lesen aus Dateien

Wenn Sie aus einer Datei lesen, ist es schneller, in einem Rutsch von der lokalen Verschlüsselung nach Unicode (QString) zu konvertieren als zeilenweise. Das bedeutet, dass Methoden wie QIODevice::readAll() meist eine gute Lösung sind, gefolgt von einer einizigen QString instantierung.

Bei größeren Dateien sollte sich in Betracht ziehen, einen Block von Zeilen zu lesen und dann zu konvertieren. Auf diese Art und Weise haben Sie die Möglichkeit ihre GUI zu aktualisieren. Das kann bewekstelligt werden, indem Sie wieder in die Ereignisschleife eintreten und gleichteitig mittels eines Timers die Blöcke im Hintergrund lesen oder indem Sie eine lokale Ereignisschleife definieren.

Man könnte auch qApp->processEvents() aufrufen, davon wird jedoch abgetraten, da es leicht zu zum Teil fatalen Problemen führt.

QString aus einem KProcess lesen

KProcess löst das Signal readyReadStandard{Output|Error} aus, sobald Daten eintreffen.

Ein häufiger Fehler ist es, alle verfügbaren Daten im verbundenen Slot zu lesen und sofort in ein QString zu konvertieren: Die Daten könnten in kleineren unregelmäßigen Portionen einreffen, daher könnten multi-byte Zeichen in Stücke geschnitten und daher ungültig werden. Mehrere Ansätze existieren:

  • Müssen Sie die Daten wirklich sofort verarbeiten sobald sie eintreffen? Wenn nicht, benutzen Sie lieber readAllStandard{Output|Error} nachdem der Prozess beendet wurde. Anders als in KDE3 ist KProcess nun in der Lage, die Daten zu sammeln.
  • Sammeln Sie die Datenbrocken im Slot und bearbeiten Sie sie immer dann, wenn ein Newline kommt oder ein bestimmter Timeout auftritt.Example code (KDE3 based)
  • Fügen Sie den Prozess in ein QTextStream ein und lesen Sie diesen zeilenweise. Während das zumindest theoretisch funktionieren sollte, funktioniert das in der Praxis derzeit nicht.

QString und QByteArray

Während QString das Werkzeug der Wahl für viele Situationen ist, in denen mit Zeichenketten gearbeitet wird, gibt es eine Situation, wo dies äußerst ineffizient ist. Wenn Sie mit Daten arbeitet, die in einem QByteArray gespeichert sind, sehen Sie sich vor, dass Sie diese nicht an Methoden übergeben, die einen QString Parameter benötigen und daraus wieder ein QByteArray macht.

For example:

QByteArray myData;
QString myNewData = mangleData( myData );

QString mangleData( const QString& data ) {
    QByteArray str = data.toLatin1();
    // mangle 
    return QString(str);
}

Der kostspielige Teil hier ist die Konvertierung nach QString, was intern als Konvertierung in Unicode durchgeführt wird. Das ist unnötig, da das erste, was die Methode tut, eine Rückkonvertierung nach toLatin1() ist. Wenn Sie sich also sicher sind, dass eine Unicode Konvertierung nicht bennötigt wird, versuchen sich Zwischenschritte mit QString zu vermeiden.

Das obige Beispiel sollte statt dessen geschrieben werden als:

QByteArray myData;
QByteArray myNewData = mangleData( myData );

QByteArray mangleData( const QByteArray& data )

QDomElement

When ein XML Dokument geparst wird, benötigt man häufig eine Iteration über alle Elemente. Man könnte versucht sein, folgenden Code dafür zu benutzen:

for ( QDomElement e = baseElement.firstChild().toElement();
      !e.isNull();
      e = e.nextSibling().toElement() ) {
       ...
}

Das ist jedoch nicht korrekt: Die obige Schleife wird vorzeitig beendet wenn es auf einen QDomNode trifft, der etwas anderes als ein Element ist (zum Beispiel ein Kommentar).

Die korrekte Schleife sieht so aus:

for ( QDomNode n = baseElement.firstChild(); !n.isNull();
      n = n.nextSibling() ) {
    QDomElement e = n.toElement();
    if ( e.isNull() ) {
        continue;
    }
    ...
}