Vorwärtsdeklaration vs include

17

Reduce the number of #include files in header files. It will reduce build times. Instead, put include files in source code files and use forward declarations in header files.

Ich habe das hier gelesen. http://www.yolinux.com/TUTORIALS/LinuxTutorialC++CodingStyle.html .

Es heißt also, dass eine Klasse (Klasse A) in der Header-Datei nicht die tatsächliche Definition einer Klasse (Klasse B) verwenden muss. Zu diesem Zeitpunkt können wir die Forward-Deklaration verwenden, anstatt die bestimmte Header-Datei (Klasse B) einzuschließen.

Frage: Wenn die Klasse (Klasse A) im Header nicht die tatsächliche Definition einer bestimmten Klasse (Klasse B) verwendet, hilft die Forward-Deklaration dann, die Kompilierungszeit zu verkürzen.

Nayana Adassuriya
quelle

Antworten:

11

Dem Compiler ist es egal, ob Klasse A Klasse B verwendet. Er weiß nur, dass Klasse A beim Kompilieren und ohne vorherige Deklaration der Klasse B (Forward-Deklaration oder auf andere Weise) in Panik gerät und sie als Fehler markiert.

Wichtig ist hierbei, dass der Compiler weiß, dass Sie nicht versucht haben, ein Programm zu kompilieren, nachdem Ihre Katze auf Ihrer Tastatur gelaufen ist und einige zufällige Buchstaben erstellt hat, die möglicherweise als Klasse interpretiert werden oder nicht.

Wenn ein Include angezeigt wird, muss es die Datei öffnen und analysieren, um die darin enthaltenen Informationen verwenden zu können (unabhängig davon, ob dies tatsächlich erforderlich ist oder nicht). Wenn diese Datei dann andere Dateien enthält, müssen auch diese geöffnet und analysiert werden usw. Wenn dies vermieden werden kann, ist es im Allgemeinen eine gute Idee, stattdessen eine Forward-Deklaration zu verwenden.

Bearbeiten : Die Ausnahme von dieser Regel sind vorkompilierte Header. In diesem Fall werden alle Header kompiliert und für zukünftige Kompilierungen gespeichert. Wenn sich die Überschriften nicht ändern, kann der Compiler die vorkompilierten Überschriften aus früheren Kompilierungen intelligent verwenden und so die Kompilierzeiten verkürzen. Dies funktioniert jedoch nur dann, wenn Sie die Überschriften nicht häufig ändern müssen.

Neil
quelle
Vielen Dank für die Erklärung. Dann ok als Beispiel denken Sie , es gibt drei Header - Dateien vehicle.h, bus.h, toybus.h. vehicle.hinclude by bus.hund bus.hinclude by toybus.h. Also, wenn ich mich etwas ändere bus.h. öffnet und parst der Compiler das vehicle.hnochmal? Kompiliert es es erneut?
Nayana Adassuriya
1
@NayanaAdassuriya Ja, wird es aufgenommen und analysiert jedes Mal, die auch ist , warum Sie sehen #pragma onceoder #ifndef __VEHICLE_H_Typdeklarationen in Header - Dateien , um solche Dateien zu verhindern , dass sie mehrmals enthalten ist (oder mehrmals im Fall von ifndef zumindest verwendet wird).
Neil
4

denn dann muss A.hpp nicht #include B.hpp

so wird a.hpp

class B;//or however forward decl works for classes

class A
{
    B* bInstance_;
//...
}

Wenn also A.hpp enthalten ist, ist B.hpp nicht implizit enthalten, und alle Dateien, die nur von A.hpp abhängen, müssen nicht bei jeder Änderung von b.hpp neu kompiliert werden

Ratschenfreak
quelle
aber in der Quelldatei (A.cpp). müssen die eigentliche Header-Datei (Bh) enthalten. Es muss also jedes Mal kompiliert werden. Schließlich muss Bh in beide Richtungen mit den Änderungen neu kompiliert werden. Irgendwie anders?
Nayana Adassuriya
@NayanaAdassuriya nein, da A nur einen Zeiger auf B verwendet und Änderungen an B keinen Einfluss auf A.hpp (oder die Dateien, die ihn enthalten) haben
Ratschenfreak
@NayanaAdassuriya: Ja, A.cpp muss neu kompiliert werden (wenn es die Definition von B in den Körpern von A verwendet, aber normalerweise), aber C.cpp, das A verwendet, aber nicht B direkt, wird dies nicht tun.
Jan Hudec
3

Denken Sie daran, dass der C / C ++ - Präprozessor ein separater, rein textueller Verarbeitungsschritt ist. Die #includeDirektive zieht den Inhalt des enthaltenen Headers ein und der Compiler muss ihn analysieren. Darüber hinaus ist die Kompilierung für jedes .cppElement vollständig getrennt, sodass die Tatsache, dass der Compiler B.hbeim Kompilieren nur analysiert wurde , B.cppkeine Rolle spielt, wenn er es beim Kompilieren erneut benötigt A.cpp. Und nochmal beim Kompilieren C.cpp. Und D.cpp. Und so weiter. Und jede dieser Dateien muss neu kompiliert werden, wenn sich eine darin enthaltene Datei geändert hat.

Angenommen, die Klasse Averwendet Klasse Bund Klassen Cund Dverwendet Klasse A, muss aber nicht manipuliert werden B. Wenn die Klasse Anur mit forward deklariert werden kann B, B.hwird sie zweimal kompiliert: beim Kompilieren B.cppund A.cpp(weil Bsie in Aden Methoden von noch benötigt wird ).

Aber wenn A.henthält B.h, wird es kompiliert vier mal-beim Kompilieren B.cpp, A.cpp, C.cppund D.cppwie die später zwei nun indirekt enthalten B.hauch.

Auch wenn der Header mehrmals enthalten ist, muss er vom Präprozessor jedes Mal gelesen werden . Es überspringt die Verarbeitung seines Inhalts aufgrund der Guarding- #ifdefAnweisungen, liest sie jedoch weiterhin und muss nach dem Ende der Guard-Anweisung suchen, was bedeutet, dass alle darin enthaltenen Präprozessor-Anweisungen analysiert werden müssen.

(Wie in der anderen Antwort erwähnt, versuchen vorkompilierte Header, dies zu umgehen, aber es handelt sich um ihre eigene Wurmbüchse. Grundsätzlich können Sie sie vernünftigerweise für Systemheader verwenden, und zwar nur, wenn Sie nicht zu viele, aber nicht für Überschriften in Ihrem Projekt)

Jan Hudec
quelle
+1, die Header-Includes werden nur dann zu einem ernsthaften Problem, wenn Sie eine ziemlich große Anzahl von Klassen haben, nicht wenn Sie nur zwei Klassen A und B haben. Alle anderen Posts scheinen diesen zentralen Punkt zu verfehlen.
Doc Brown
2

Eine Forward-Deklaration kann viel schneller analysiert werden als eine ganze Header-Datei, die selbst möglicherweise noch mehr Header-Dateien enthält.

Wenn Sie etwas in der Header-Datei für Klasse B ändern, muss alles, einschließlich dieses Headers, neu kompiliert werden. Bei einer Forward-Deklaration ist dies möglicherweise nur die Quelldatei, in der sich die Implementierung von A befindet. Wenn jedoch der Header von A tatsächlich den Header von B enthält, wird auch alles, was eingeschlossen a.hppist, neu kompiliert, selbst wenn nichts von B verwendet wird.

Benjamin Kloster
quelle