Aufteilung des Sourcecodes in mehrere Dateien (+Header)
-
Du hast ein Semikolon am ende der Funktion in der Header vergessen. Noch als Tipp using namespace std nicht verwenden.
-
Hallo nochmal!
VIELEN DANK!
Ich habe <string> in system.h inkludiert und das fehlende Semikolon angehangen (
) und es funktioniert. Ich dachte allerdings, es wäre eine Art NO-GO innerhalb eines Headers andere Header zu inkludieren.
Das mit der Vorwärtsdeklarations kannte ich in dem Sinne noch nicht, scheint aber logisch zu sein.
Da mein Ursprünglicher Quelltext (main) ja noch ein ganzen Stück weiter aufgeteilt werden soll, rechne ich einfach mal damit, dass ich bald wieder so ein Problem bekomme....
Ich fasse deswegen mal zusammen wie ich vorgehen würde:
- Neue Datei mit Header erstellen
- Funktion in die *.cpp und Prototyp, Variablen, etc in die *.h
- xxx.h in die xxx.cpp inkludieren lassen
- Alle benötigten Header der Funktionen innerhalb der xxx.h inkludieren (auch in die xxx.cpp???)
- Fertig?Danke nochmal für die Hilfe!
-
thief1064 schrieb:
Ich dachte allerdings, es wäre eine Art NO-GO innerhalb eines Headers andere Header zu inkludieren.
Vielleicht hast du das ja im Zusammenhang mit
using namespace std;
gehört. Man versucht allerdings relativ wenige (d. h. die nötigen) Header in Headern einzubinden, weil es die Zeit zum Erstellen des Programms erheblich beeinflussen kann.Ich fasse deswegen mal zusammen wie ich vorgehen würde:
- Neue Datei mit Header erstellen
- Funktion in die *.cpp und Prototyp, Variablen, etc in die *.h
- xxx.h in die xxx.cpp inkludieren lassen
- Alle benötigten Header der Funktionen innerhalb der xxx.h inkludieren (auch in die xxx.cpp???)
- Fertig?Vom groben Ablauf passt das. Allerdings solltest du keine globalen Variablen haben und die benötigten Header erst da inkludieren, wo sie auch gebraucht werden. Wenn eine Implementierung in der cpp-Datei einen Header braucht, die dazugehörige h-Datei allerdings nicht, sollte der Header natürlich erst in der cpp-Datei eingebunden werden.
-
Das mit dem NO-Go habe ich scheinbar wirklich verwechselt. Habe es nämlich vorhin schon mal im Zusammenhang mit using namespace gelesen.
Ich bin gerade dabei eine weitere Funktion auszulagern und bin nun auf das Problem gestoßen, dass ich eben globale Variablen verwende, was nach dem Schema ja nicht möglich ist (oder doch?).
Jedenfalls zeigt mir der Linker jetzt immer die Meldung an, dass meine globalen Variablen bereits definiert wurden.
error LNK2005: "union SDL_Event event" (?event@@3TSDL_Event@@A) already defined in main.obj
Die globals.h (Header mit globalen Variablen) wird nämlich zur Zeit sowohl in main.cpp als auch in system.cpp / system.h inkludiert.
Was macht man da am besten?
-
Dafür gibts das Schlüsselwort
extern
. Aber wie gesagt solltest du eher die globalen Variablen loswerden.
-
Naja, ich habs jetzt aufgegeben.
Ich spar mir lieber den Frust mit den verdammten Linker Fehlern, weil irgendwo wieder was nicht inkludiert wurde (oder zu viel) und realisier das Projekt lieber in C#.
-
thief1064 schrieb:
Naja, ich habs jetzt aufgegeben.
Ich spar mir lieber den Frust mit den verdammten Linker Fehlern, weil irgendwo wieder was nicht inkludiert wurde (oder zu viel) und realisier das Projekt lieber in C#.Das würde ich nicht so machen. Es kostet ein bisschen Aufwand, zu verstehen, was da eigentlich passiert, aber dann sollte alles klar sein.
Mal ein paar Regeln:
1.1 Beim Includen von Headern (*.h, .hpp) wird lediglich der Inhalt Buchstabe-für-Buchstabe vom Compiler in eine C++-Datei (.cpp, *.cxx, *.cc) eingefügt.
1.2 Wenn der Compiler wissen will, welchen Typ eine Variable/Funktion hat, muss man die Variable vorher deklarieren bzw. die Funktion vorher deklarieren oder implementieren oder beides. Damit man das dem Compiler nicht für jede Datei neu mitteilen muss, kann man die ganzen "Bekanntmachungen" in den Header auslagern. Außerdem muss man dadurch die Bekanntmachung nur einmal ändern, wenn man den Typ der tatsächlichen Variable/Funktion ändert.2. Funktionen darf man nur einmal definieren (also implementieren) und Variablen nur einmal deklarieren, ansonsten sind sie für den Linker in mehreren Objektdateien, die aus den C++-Dateien erstellt werden, vorhanden und das kann er nicht verarbeiten, da er sich nun mal nicht entscheiden kann.
=> Man sollte Funktionsdefinitionen und Variablendeklarationen NUR IN C++-DATEIEN MACHEN.
---- Außer: man macht Funktionen/Variablen static, dann sind sie immer nur in einer C++-Datei gültig. Dazu zählen auch inline-Funktionen, die automatisch static sind. Eine andere Ausnahme sind const-Variablen.3. Will man dennoch im Header klar stellen, dass die Funktion/Variable existiert, muss man
3.1 Funktionen deklarieren (Bsp. "void print_something();")
3.2 Variablen als extern deklarieren (Bsp. "extern int error_count"). Das sagt dem Compiler, dass die Variable "irgendwo anders", also extern definiert und damit ihr Speicherbereich reserviert ist. Man kann aber trotzdem eine extern-Deklaration und eine Deklaration einer Variablen in der selben Datei haben.4. Template-Funktionen sind, sofern sie nicht vollständig spezialisiert werden, static und damit im Header implementierbar. Wenn sie dennoch in einer C++-Datei implementiert werden, sind sie auch nur für diese Datei gültig, da helfen Deklarationen auch nichts. Der Grund ist, dass der Compiler beim kompilieren einer C++-Datei die Template-Funktion für jeden unterschiedlichen Template-Typ neu anlegen muss. Beispiel:
template<typename T> T add(T const &first, T const &second) { return first + second; }
Da diese Funktion bei jedem Typ einen anderen Binärcode erzeugt, muss sie der Compiler bei jedem Funktionsaufruf neu anlegen. Dabei kann er innerhalb einer C++-Datei Funktionen mit gleichem Typ, die mehrfach aufgerufen werden, nur einmal anlegen, was aber innerhalb der anderen C++-Dateien passiert, ist ihm nicht bekannt. Daher können diese angelegten Funktionen nur für eine Datei gelten, weil es sonst mehrfache Definitionen gäbe, falls die Funktion in mehreren Dateien für den gleichen Typ angelegt würde. Ist die Template-Funktion jetzt in Datei 1 implementiert und wird in Datei 2 aufgerufen, weiß der Compiler nicht, ob sie in Datei 1 für diesen Typ angelegt ist. Wenn aber nur die Deklaration vorhanden ist, kann er sie nicht neu anlegen, daher muss er einen Fehler ausgeben.
Template-Funktionen sollten also entweder direkt im Header implementiert/definiert werden, oder (wie auch oft praktiziert) in einer ".impl"-Datei, die dann im Header inkludiert wird.Als Gegenbeispiel dazu sieht eine voll spezialisierte Template-Funktion so aus:
template<typename T> class mytype //Template-Klasse, muss in die Headerdatei { public: static T add(T const &first, T const &second) //statische Funktion, Aufruf z.B.: "mytype<double>::add(1.0, 2.0)" { cout << "Int specialization\n"; return first + second; } }; template<> int mytype<int>::add(int const &first, int const &second) //Spezialisierung, muss in die C++-Datei, Deklaration in die Headerdatei { return first + second; }
5. Damit Klassen Pointer auf andere Klassen verwenden können, muss bekannt sein, dass es diese andere Klasse gibt.
Das kann in dieser Form passierenclass myclass { //private Variablen/Funktionsdeklarationen public: //öffentliche Funktionsdeklarationen/Variablen };
Enthalten aber zwei Klassen Pointer auf Objekte der jeweils anderen, ist eine Vorwärtsdeklaration nötig:
class B; class A { B *b; public: //... }; class B { A *a; public: //... };
Das gilt nur für Pointer und Referenzen, aber nicht für normale Objekte, mit denen so etwas aus gutem Grund nicht geht (ansonsten würden die Objekte, die auch noch andere Variablen enthalten, unendlich groß werden).
Grundsätzlich kann man sich merken: Wenn der Compiler die Größe oder den Aufbau einer Variable (des Typs) wissen muss, reicht eine Vorwärtsdeklaration nicht aus.
6. Man muss vermeiden, dass Header sich gegenseitig inkludieren. Ansonsten würde wie bei Klassen, die ein Objekt von der jeweils anderen Klasse enthalten, die Header unendlich oft inkludiert werden. Um zusätzlich zu vermeiden, dass Header mehrfach inkludiert werden, sollte man Header Guards verwenden:
Utils.h:
#ifndef UTILS_H //wenn es nicht definiert ist, wurde der Header vorher noch nicht inkludiert #define UTILS_H //jetzt ist es aber definiert, d.h. der Header wird nicht nochmal inkludiert #include <string> class MyUTFString { //... }; #endif //UTILS_H
Strings.h:
#ifndef STRINGS_H #define STRINGS_H #include "Utils.h" //Funktionen, die UTFString benutzen #endif //STRINGS_H
Numbers.h:
#ifndef NUMBERS_H #define NUMBERS_H #include "Utils.h" //Funktionen, die UTFString benutzen #endif //NUMBERS_H
main.cpp:
#include "Strings.h" //Utils.h wird inkludiert #include "Numbers.h" //Utils.h wird dank der Include Guards nicht nochmal inkludiert int main() { //... }
7.1 Membervariablen in Klassen dürfen im Header deklariert sein, da sie beim Ausführen des Programms nicht einfach global auf dem Stack angelegt werden, sondern nur darstellen, wie der Speicher einer Klasse interpretiert werden soll.
Das heißt:class ABC { int x; //die Variablendeklarationen hier dürfen und sollten in den Header string y; public: //... }
7.2 Funktionen dürfen auch direkt in der normalen Klassendeklaration implementiert werden, dann sind sie automatisch inline und somit auch static. Dieses Vorgehen ist jedoch nur bei Template-Funktionen und eventuell bei Laufzeitkritischen Funktionen ratsam.
Beispiel:
class ABC { int tmp; public: void Compute_tmp_value(int param); //berechnet den Wert von tmp, nicht Laufzeitkritisch, wird nur einmal benutzt template<typename T> void Assign_Pointer_Value(T *in) //Template-Funktion, kommt in die Klassendeklaration im Header { tmp = reinterpret_cast<int>(in); } int GetAdded(int in) //kurz und daher lohnt sich ein extra Aufruf weniger -> inline in der Klassendeklaration { return tmp + in; } };
Für Templatefunktionen kann man auch alternativ folgende Syntax wählen:
template<typename NumberType> class ABC { NumberType tmp; public: template<typename T> void Assign_Pointer_Value(T *in); }; template<typename NumberType, typename T> void ABC<NumberType>::Assign_Pointer_Value(T *in) { tmp = reintepret_cast<int>(in); }
7.3 statische Membervariablen in Klassen müssen in C++-Dateien nochmals angelegt werden, da sie sich wie globale Variablen verhalten und ihren eigenen Speicher auf dem Stack benötigen.
Beispiel:
Header:class ABC { public: static int abc; };
C++-Datei:
int ABC::abc = 5;
8. Enumerations kommen in den Header, da ihre Werte bereits zur Compilezeit bekannt sind und so einfach wie bei #defines im Sourcecode verwendet werden können.
Beispiel:
//Header enum Essen { ESSEN_NICHTS = 0, ESSEN_BROT, ESSEN_GEMUESE, ESSEN_OBST, ESSEN_ANDERS };
-
Kommt das eigentlich mal in den FAQ?
-
Kleine Frage: Ist es nicht schöner, Makro statt #define zu sagen?
-
Hacker schrieb:
Kleine Frage: Ist es nicht schöner, Makro statt #define zu sagen?
Natürlich, aber vielleicht wissen manche nicht, was ein Makro ist. Bei #define ist es hingegen relativ klar.