C++ Header Dateien - Klassen wozu doppelt einbinden

Tockra

Lt. Commander
Registriert
Dez. 2008
Beiträge
1.058
Hey Leute,

ich wollte einmal wissen, wieso ich die Headerdatei einer Klasse sowohl im Quelltext der Klasse, als auch im Quelltext der Main einbinden sollte?
Wenn die Teile zsm. compiliert werden, dann werden die doch sowieso in eine Datei kopiert und compiliert, dann würde ich ja den Header der Klasse einmal zuviel einbinden!? Bzw. wenn die Quelldatei der Klasse über die Main kopiert werden würde, könnte ich mir das einbinden der Headerdatei bei der Main doch sparen !?

Gruß
T
 
Die Teile werden aber nicht zusammen kompiliert. Es wird zuerst aus jeder Quelldatei eine Objektdatei erzeugt, und diese werden dann in einem zweiten Schritt zusammen gelinkt. Deswegen muss jede Quelldatei alle Header einbinden, aus der sie Klassen, Funktionen oder andere Dinge benutzt.
 
Das kommt drauf an:
Wenn du die Klasse benutzt, brauchst du den Header. Eventuell reicht auch eine forward declaration.

// myclass.hpp
Code:
class myclass
{
public:
    void func();
    int member;
}

// myclass.cpp
Code:
void myclass::func()
{
    // my implementation ...
}

// main.cpp
Code:
class myclass; // forward declaration
#include "myclass.h" // definition needed

int main(int argc, char* argv[])
{
    myclass* ptr = nullptr; // only forward declaration
    myclass obj; // definition needed -> #include "myclass.hpp"
    obj.func();
    myclass
    return 0;
}

Ich gebe Dir recht, vieles könnte der Compiler selber machen. Der Grund hierfür liegt im C++ compilation model, also viele Objektdateien werden unabhängig kompiliert und dann zusammen gelinkt. Insbesondere mit Templates und zyklischen Referenzen muss man aufpassen. Ich hoffe mit C++17 kommen endlich Module, dann braucht man #include für vieles hoffentlich nicht mehr.
 
Zuletzt bearbeitet:
Im Header schreibst du doch eh (bzw sollte man doch)

Code:
#ifndef _HEADER_H_
#define _HEADER_H_

...Code...

#endif

Von daher kann ja nichts passieren, wenn du es "zu oft" einbindest
 
Danke für die Antworten. Führt die Mechanik dann nicht dazu, dass wenn alle einzelnd übersetzt werden, dass ein bestimmter Programmteil x mal in dem Endprogramm ist, wenn in jedem übersetzten Objekt z.B. der Header von einer bestimmten Klasse drin ist !?

dominic.e schrieb:
Im Header schreibst du doch eh (bzw sollte man doch)

Code:
#ifndef _HEADER_H_
#define _HEADER_H_

...Code...

#endif

Von daher kann ja nichts passieren, wenn du es "zu oft" einbindest

Jo, das ist klar, deswegen frag ich mich ob man eines der 2 includes nicht einfach weglassen sollte, da das eine eh nie ausgeführt wird.
 
Tockra schrieb:
Danke für die Antworten. Führt die Mechanik dann nicht dazu, dass wenn alle einzelnd übersetzt werden, dass ein bestimmter Programmteil x mal in dem Endprogramm ist, wenn in jedem übersetzten Objekt z.B. der Header von einer bestimmten Klasse drin ist !?

Im Header sind meist nur Deklarationen, nur selten Definitionen. Konstanten tauchen tatsächlich in allen Übersetzungseinheiten, die den entsprechenden Header angezogen haben auf; gleiches gilt für instanziierte Funktionstemplates ... das darf dann später der Linker aber wieder wegoptimieren, wenn er die Übersetzungseinheiten zusammenlinkt.

Jo, das ist klar, deswegen frag ich mich ob man eines der 2 includes nicht einfach weglassen sollte, da das eine eh nie ausgeführt wird.

Probier's doch mal. Du wirst sehen, daß die Übersetzungseinheit, aus der du das include entfernt hast, sich nicht mehr kompilieren lassen wird. Der Compiler behandelt jede Übersetzungseinheit separat, und es interessiert ihn nicht im Geringsten, ob du einen Header, den er für diese Übersetzungseinheit braucht, in Übersetzungseinheit xyz schon mal eingezogen hast.
 
Ich versuch nochmal das c++ compilation Model zu erklären.

Grundsätzlich betrachtet der Compiler jede Übersetzungseinheit unabhängig voneinander und erst der Linker fügt dann die Ergebnisse der einzelnen Übersetzungsergebnisse zu einem Programm zusammen. Streng genommen sieht der Compiler die Headerdateien noch nicht mal als separate Datei. Stattdessen kopiert die include Direktive (Konzeptionell) einfach den Inhalt der angegebenen Textdatei (muss keine Headerdatei sein) in die Aufrufende Datei und für den Compiler sieht das dann so aus, als ob das alles in einer einzigen Datei gestanden hätte. Daher wird auch immer von translation units gesprochen und nicht von Quelltextdateien.
Es gibt übrigens auch sogenannte Unity-Builds, bei denen sämtliche .cpp-Dateien des Projekts in eine einzelne Masterdatei "kopiert" werden und die dann an den Compiler verfüttert wird.
Die Include Guards sollen nur verhindern, dass der selbe Text mehrfach in die selbe Übersetzungseinheit kopiert wird. Wenn du z.B. in der "main.cpp" die Dateien "A.h" und "B.h" einbindest und beide wiederum auf "C.h" verweisen, dann würde im Ergebnis zweimal der Text von "C.h" stehen. Das hat aber keine Auswirkungen auf andere translation units

Jetzt noch zum Thema, warum man Headerdateien überhaupt braucht und warum man sie "mehrfach" einbinden muss. Wie gesagt sieht der Compiler immer nur eine Translation Unit auf einmal. Um Funktionen aus anderen Übersetzungseinheiten aufrufen zu können, muss dem Compiler erstmal mitgeteilt werden, wie deren Interface aussieht. Dies wird durch Deklarationen gemacht (Funktionskopf ohne Body).
Da der Compiler sowieso nicht zwischen dem Text aus der Ursprünglichen Quelldatei und den hineinkopierten Text aus Headerdateien unterschiedet ist es erstmal egal, wo du diese Deklarationen hinschreibst. Grundsätzlich muss aber jede Funktion in jeder Übersetzungseinheit, in der sie genutzt werden soll, erneut deklariert werden (der Compiler fängt ja wieder bei Null an). Wenn eine Funktion also in 10 Übersetzungseinheiten genutzt werden soll muss sie auch 10 mal deklariert werden - da ist es natürlich simpler diese Deklaration einmal in eine separate Headerdatei zu schreiben, von wo sie dann bei Bedarf kopiert werden kann.
 
Sehr schön erklärt Miuwa, +1 :daumen:
 
Zurück
Oben