Hallo da draußen,
heute mal wieder etwas C++-Sachen.
In der Regel inkludiert man in C++-Klassen, speziell wenn es um Qt geht, verschiedene andere Klassen, seien es jetzt QString’s, QList-Elemente oder eben selbst definierte Klassen, die verschiedene Business-Logik kapseln. Diese packt man dann manchmal auch in member-Variablen oder nutzt diese in privaten oder public-Methoden als Übergabe-Parameter.
Prinzipiell gibt es zwei Methoden, um Klassen einzubinden:
- direkte includes in der h-Datei
- Forward-Deklaration der Klasse
Für Neulinge erkläre ich mal die beiden Dinge:
Include in der h-Datei
Hier mal ein Beispiel-Code für ein Klassen-Header, die einen Methode hat, der ein QString übergeben wird. Die Datei speichere ich mal unter testklasse.h ab.
#ifndef TESTKLASSE_H #define TESTKLASSE_H #include <QObject> #include <QString> class TestKlasse : public QObject { public: TestKlasse(); //Konstruktor der Klasse QString getHelloWorld() const; } #endif // TESTKLASSE_H
Normalerweise generiert Qt die meisten Sachen selbst. Trotzdem kurze Erklärung:
Die erste Zeile wird dazu genutzt, um h-Dateien nicht mehrfach zu laden. Wird eine h-Datei inkludiert, wird geprüft, ob das “Makro” TESTKLASSE_H schon einmal definiert (also geladen) wurde. Ist dies nicht der Fall, wird dieses “Makro” definiert (2.Zeile).
In der dritten und vierten Zeile werden die entsprechenden referenzierten / genutzten Klassen, nämlich QObject und QString geladen
Dann wird die Klasse definiert als Unterklasse von QObject und Konstruktor und eine Methode getHelloWorld() definiert.
Die letzte Zeile definiert dann das Ende des Makros (Laden der TestKlasse).
Soweit, sogut. Durch die “#include”-Anweisungen haben wir entsprechend die genutzten Klassen direkt im H-File geladen. Die Klassendefinition steht nun sowohl im h-File als auch im zugehörigen cpp-File (also der Implementierung) zur Verfügung.
Nun noch die zugehörige Cpp-Datei (testklasse.cpp)
#include "testklasse.h" //Definition des Konstruktors TestKlasse::TestKlasse() : public QObject() { } QString TestKlasse::getHelloWorld() { return QString("Hello World!"); }
Wie man sieht, benötigt man hier keinen Include von QString und QObject mehr.
Forward-Deklaration der Klasse
Prinzipiell kann man die includes aber auch in die Implementierung verlagern. Dazu mal wieder unsere modifzierte h-Datei testklasse.h:
#ifndef TESTKLASSE_H #define TESTKLASSE_H class QString; class QObject; class TestKlasse : public QObject { public: TestKlasse(); //Konstruktor der Klasse QString getHelloWorld() const; } #endif // TESTKLASSE_H
Hier fällt auf, dass nun keinerlei Includes in der Datei vorhanden sind. Dafür werden die Klassen QString und QObject in Zeile 4 und 5 Forward-Deklariert. Die Forward-Deklaration reserviert quasi den Namen der Klasse, die “Implementierung” erfolgt dann in der cpp-Klasse. Das heißt, in der cpp-Klasse müssen dann die eigentlichen Includes gemacht werden.
Somit sieht unsere testklasse.cpp folgendermaßen aus:
#include "testklasse.h" #include <QObject> #include <QString> //Definition des Konstruktors TestKlasse::TestKlasse() : public QObject() { } QString TestKlasse::getHelloWorld() { return QString("Hello World!"); }
Der Unterschied zum ersten Fall (include in h-Datei) ist, dass die includes in Zeile 2 und 3 nun im cpp-File zwingend definiert sein müssen.
Selbstverständlich kann man die Dinge auch mischen, d.h. bestimmte Klassen übers h-File laden und andere Klassen über Forward-Deklaration laden.
Da ich primär aus dem Java-Bereich komme und mir C++ später erst selbst angeeignet habe, war für mich die erste Methode immer die Wahl, auch wenn das nicht immer zu 100% funktioniert hat. Speziell, wenn man Kreuzabhängigkeiten zwischen Klassen besitzt, z.B. Klasse A beinhaltet eine Liste von Klasse B, die wiederum einen Rück-Verweis (als Pointer) auf die Klasse A besitzt. Wenn beide jeweils in der h-Datei auf das jeweilig andere verweisen, kommt ein großes Kuddelmuddel heraus und der C++-Compiler steigt aus.
In solchen Fällen [b]MUSS[/b] man eine Forward-Deklaration benutzen.
Wann nutzt man also nun welche Methode?
Vorteil der ersten Variante ist die klar definierte Struktur. Durch Anschauen der h-Datei bekommt man sofort einen Überblick, welche weiteren h/cpp-Dateien entsprechend genutzt werden. Speziell zur Erzeugung von Klassenbäumen ist das nett.
Großer Nachteil ist, dass bei irgendeiner Änderung der h-Datei automatisch auch alle abhängigen Klassen mit übersetzt werden (inkl. h-Dateien). Das führt im schlimmsten Fall dazu, dass bei Änderung einer h-Datei das gesamte Projekt neu übersetzt werden muss, wenn denn z.B. die h-Datei eine essentielle, überall genutzte Datei ist. Bei größeren Projekten geht so ziemlich viel Compile-Zeit verloren.
Das ist wiederum der Vorteil der zweiten Variante. Dort werden nur die Klassen, die direkt die geänderte Klasse (mit dem geänderten h-File) nutzen übersetzt, die h-Datei bleibt entsprechend unangerührt. Klassen, wiederum auf diese h-Datei verweisen müssen dann nicht mehr übersetzt werden.
Hier spart man sich wirklich viel Compile-Zeit.
Ein weiterer Vorteil ist, dass man z.B. unterschiedliche Implementierungen vornehmen kann, je nach Qt-Version, Makros.
Ich persönlich nutze aktuell eine Misch-Variante. Da sich Qt-Klassen i.d.R. nicht ändern, inkludiere ich diese über die erste Methode, also direkt im h-File und nutze die Forward-Deklaration für meine eigenen Klassen.
Das spart dann Compile-Zeit und zumindest, was an Qt-Klassen inkludiert wird, kann ich direkt aus dem h-File ersehen.