3 Stimmen

Frage zu C++-Header-Dateien

Ich habe bei der Arbeit mit Klassen etwas C++-Code ausprobiert, und dabei ist mir diese Frage aufgefallen, die mich ein wenig stört.

Ich habe eine Header-Datei erstellt, die meine Klassendefinition enthält, und eine cpp-Datei, die die Implementierung enthält.

Wenn ich diese Klasse in einer anderen cpp-Datei verwende, warum schließe ich dann die Header-Datei ein, anstatt die cpp-Datei, die die Klassenimplementierungen enthält?

Wenn ich die Klasse Implementierungsdatei einschließen, dann die Klasse Header-Datei sollte automatisch richtig importiert werden (da ich bereits die Header-Datei in der Implementierungsdatei enthalten)? Ist das nicht viel natürlicher?

Entschuldigung, wenn dies eine dumme Frage ist, ich bin wirklich daran interessiert zu wissen, warum die meisten Leute .h anstelle von .cpp-Dateien einschließen, wenn die letztere natürlicher scheint (ich weiß, Python etwas, vielleicht ist das, warum es natürlich zu mir zumindest scheint). Ist es nur historisch bedingt oder gibt es einen technischen Grund, der mit der Programmorganisation zu tun hat oder vielleicht etwas anderes?

14voto

David Z Punkte 121773

Denn wenn Sie eine andere Datei kompilieren, muss C++ eigentlich nichts über die Implementierung wissen. Es muss nur die Unterschrift jeder Funktion (welche Parameter sie annimmt und was sie zurückgibt), den Namen jeder Klasse, welche Makros es gibt #define d und andere "zusammenfassende" Informationen wie diese, so dass überprüft werden kann, ob Sie die Funktionen und Klassen korrekt verwenden. Der Inhalt der verschiedenen .cpp Dateien werden erst zusammengefügt, wenn der Linker läuft.

Nehmen wir zum Beispiel an, Sie haben foo.h

int foo(int a, float b);

y foo.cpp

#include "foo.h"
int foo(int a, float b) { /* implementation */ }

y bar.cpp

#include "foo.h"
int bar(void) {
    int c = foo(1, 2.1);
}

Wenn Sie kompilieren foo.cpp wird es foo.o , und wenn Sie kompilieren bar.cpp wird es bar.o . Bei der Kompilierung muss der Compiler nun prüfen, ob die Definition der Funktion foo() en foo.cpp deckt sich mit der Verwendung der Funktion foo() en bar.cpp (d.h. nimmt eine int und eine float und gibt eine int ). Dies geschieht dadurch, dass Sie die gleiche Header-Datei in beide .cpp Dateien, und wenn sowohl die Definition als auch die Verwendung mit der Deklaration im Header übereinstimmen, dann müssen sie auch miteinander übereinstimmen.

Der Compiler enthält jedoch nicht die Implementierung von foo() en bar.o . Es enthält lediglich einen Assembler-Befehl zur call foo . Wenn sie also eine bar.o muss es nichts über den Inhalt von foo.cpp . Wenn Sie jedoch zur Verknüpfungsphase kommen (die nach der Kompilierung stattfindet), muss der Linker tatsächlich über die Implementierung von foo() denn sie wird diese Implementierung in das endgültige Programm aufnehmen und die call foo Anweisung mit einer call 0x109d9829 (oder wie auch immer sie die Speicheradresse der Funktion foo() sein sollte).

Beachten Sie, dass der Linker die no prüfen, ob die Implementierung von foo() (in foo.o ) stimmt überein mit der Verwendung von foo() (in bar.o ) - zum Beispiel prüft es nicht, ob foo() wird aufgerufen mit einer int und eine float Parameter! Es ist ziemlich schwierig, diese Art von Überprüfung in Assembler durchzuführen (zumindest schwieriger als die Überprüfung des C++-Quellcodes), so dass der Linker darauf angewiesen ist, zu wissen, dass der Compiler dies bereits überprüft hat. Und deshalb braucht man die Header-Datei, um dem Compiler diese Informationen zu liefern.

1voto

lornova Punkte 6177

Die Magie wird durch den Linker erledigt. Jede .cpp erzeugt beim Kompilieren eine Zwischenobjektdatei mit allen exportierten und importierten Symbolen in einer Tabelle. Der Linker gleicht sie ab. Mit anderen Worten: Sie müssen nur den Header einbinden, und jedes Mal, wenn Sie die eingebundene Klasse referenzieren, wird der Compiler die Signatur der referenzierten Klasse in die Symboltabelle eintragen.

Wenn Sie die .cpp-Datei einbinden, wird derselbe Code zweimal kompiliert, und es kommt zu Linking-Fehlern, da das gleiche Symbol zweimal vom Linker gefunden wird und daher mehrdeutig ist.

0voto

pts Punkte 70857

Ein technischer Grund ist die Kompilierungsgeschwindigkeit. Nehmen wir an, Ihre Klasse verwendet 10 andere Klassen (z. B. als Typen für Mitgliedsvariablen). Das Einbeziehen der langen .cpp-Dateien für alle 10 Klassen würde die Kompilierung Ihrer Klasse erheblich verlangsamen (d.h. vielleicht 2 Sekunden statt 1 Sekunde).

Ein weiterer Grund ist das Verstecken der Umsetzung. Nehmen wir an, Sie schreiben eine Klasse, die von 10 anderen Teams in Ihrem Unternehmen verwendet werden soll. Alles, was sie über Ihre Klasse wissen und lernen müssen, steht in der .h-Datei (öffentliche Schnittstelle). In der .cpp-Datei (Implementierung) können Sie tun und lassen, was Sie wollen, Sie können sie so oft ändern, wie Sie wollen, es wird sie nicht interessieren. Aber wenn Sie die .h-Datei ändern, müssen sie möglicherweise ihren Code mit Ihrer Klasse anpassen.

Für jeden Methodenkörper können Sie entscheiden, ob Sie ihn in die .h-Datei oder in die .cpp-Datei einfügen. Wenn er in der .h-Datei steht, kann der Compiler ihn beim Aufruf einbinden, was den Code etwas schneller machen kann. Aber die Kompilierung wird langsamer, und die temporären .o (.obj)-Dateien können größer werden (weil jede von ihnen den kompilierten Methodenrumpf enthält), und die Programm-Binärdatei (.exe) kann größer werden, weil der Funktionsrumpf so oft Platz braucht, wie er inline ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X