Sind Header-Dateien wirklich gut? [geschlossen]

20

Ich finde Header-Dateien beim Durchsuchen von C ++ - Quelldateien nützlich, da sie eine "Zusammenfassung" aller Funktionen und Datenelemente in einer Klasse enthalten. Warum haben so viele andere Sprachen (wie Ruby, Python, Java usw.) keine solche Funktion? Ist dies ein Bereich, in dem die Ausführlichkeit von C ++ nützlich ist?

Matt Fichman
quelle
Ich bin mit den Werkzeugen in C und C ++ nicht vertraut, aber die meisten IDEs / Code-Editoren, in denen ich gearbeitet habe, haben eine "Struktur" - oder "Gliederungs" -Ansicht.
Michael K
1
Header-Dateien sind nicht gut - sie sind notwendig! - Siehe auch diese Antwort auf SO: stackoverflow.com/a/1319570/158483
Pete

Antworten:

31

Ursprünglicher Zweck von Header-Dateien war es, die Kompilierung in einem Durchgang und die Modularität in C zu ermöglichen. Indem die Methoden deklariert wurden, bevor sie verwendet wurden, war nur ein einziger Kompilierungsdurchgang zulässig. Dieses Zeitalter ist lange vorbei, da unsere leistungsstarken Computer problemlos und manchmal sogar schneller als C ++ - Compiler Kompilierungen mit mehreren Durchläufen durchführen können.

C ++ war abwärtskompatibel mit C, um die Header-Dateien zu behalten, fügte jedoch eine Menge hinzu, was zu einem recht problematischen Design führte. Mehr in FQA .

Aus Gründen der Modularität wurden Header-Dateien als Metadaten zum Code in Modulen benötigt. Z.B. Welche Methoden (und in C ++ - Klassen) sind in welcher Bibliothek verfügbar? Es war offensichtlich, dass Entwickler dies schreiben mussten, da die Kompilierungszeit teuer war. Heutzutage ist es kein Problem, dass der Compiler diese Metadaten aus dem Code selbst generiert. Java- und .NET-Sprachen tun dies normalerweise.

Also nein. Header-Dateien sind nicht gut. Sie waren, als wir noch Compiler und Linker auf separaten Disketten haben mussten und die Kompilierung 30 Minuten dauerte. Heutzutage stören sie nur noch und sind Zeichen für schlechtes Design.

Euphorisch
quelle
8
Ich stimme den meisten Ihrer Ausführungen zu, aber der letzte Absatz macht die Dinge ein wenig zu einfach. Header-Dateien dienen weiterhin als nützliche Funktion zum Definieren öffentlicher Schnittstellen.
Benjamin Bannier
3
@honk Darum ging es mir bei meiner Frage. Aber ich denke, dass moderne IDEs (wie von ElYusubov vorgeschlagen) dies irgendwie negieren.
Matt Fichman
5
Sie könnten hinzufügen, dass das C ++ - Komitee an Modulen arbeitet, die es C und C ++ ermöglichen würden, ohne Überschriften zu arbeiten ODER mit Überschriften, die effizienter verwendet werden. Das würde diese Antwort fairer machen.
Klaim
2
@MattFichman: Das Generieren einer Header-Datei ist eine Sache (was die meisten Editoren / IDEs des Programmierers auf irgendeine Weise automatisch tun können), aber der Punkt an einer öffentlichen API ist, dass sie effektiv von einem tatsächlichen Menschen definiert und entworfen werden muss.
Benjamin Bannier
2
@honk Ja. Es gibt jedoch keinen Grund, dies in separaten Dateien zu definieren. Es kann sich um denselben Code wie die Implementierung handeln. Genau wie C # es tut.
Euphoric
17

Während sie für Sie als Dokumentationsform nützlich sein können, ist das System, das Header-Dateien umgibt, außerordentlich ineffizient.

C wurde so entworfen, dass jeder Kompilierungsdurchlauf ein einzelnes Modul erstellt. Jede Quelldatei wird in einem separaten Durchlauf des Compilers kompiliert. Header-Dateien hingegen werden für jede der Quelldateien, die auf sie verweisen, in diesen Kompilierungsschritt eingefügt.

Dies bedeutet, dass Ihre Header-Datei, wenn sie in 300 Quelldateien enthalten ist, während der Erstellung Ihres Programms 300 Mal analysiert und kompiliert wird. Genau das Gleiche mit genau dem gleichen Ergebnis, immer und immer wieder. Dies ist eine enorme Zeitverschwendung und einer der Hauptgründe, warum die Erstellung von C- und C ++ - Programmen so lange dauert.

Alle modernen Sprachen vermeiden absichtlich diese absurde kleine Ineffizienz. Stattdessen werden normalerweise in kompilierten Sprachen die erforderlichen Metadaten in der Build-Ausgabe gespeichert, sodass die kompilierte Datei als eine Art Nachschlagewerk fungiert, das beschreibt, was die kompilierte Datei enthält. Alle Vorteile einer Header-Datei, die automatisch und ohne zusätzlichen Arbeitsaufwand erstellt wird.

Abwechselnd in interpretierten Sprachen bleibt jedes Modul, das geladen wird, im Speicher. Wenn Sie auf eine Bibliothek verweisen oder diese einbeziehen oder eine Bibliothek benötigen, wird der zugehörige Quellcode gelesen und kompiliert, der bis zum Ende des Programms gespeichert bleibt. Wenn Sie es auch an anderer Stelle benötigen, ist keine zusätzliche Arbeit erforderlich, da es bereits geladen wurde.

In beiden Fällen können Sie die in diesem Schritt erstellten Daten mit den Sprachwerkzeugen "durchsuchen". Normalerweise wird die IDE einen Klassenbrowser haben. Und wenn die Sprache eine REPL hat, kann sie häufig auch verwendet werden, um eine Dokumentationszusammenfassung aller geladenen Objekte zu erstellen.

tylerl
quelle
5
Nicht ganz richtig - die meisten, wenn nicht alle Compiler kompilieren die Header so, wie sie sie sehen, so dass es nicht schlimmer ist, sie in die nächste Kompilierungseinheit aufzunehmen, als den Block vorkompilierten Codes zu finden. Es ist nicht anders als beispielsweise .NET-Code, der wiederholt system.data.types für jede kompilierte C # -Datei erstellt. Der Grund, warum C / C ++ - Programme so lange dauern, ist, dass sie tatsächlich kompiliert (und optimiert) werden. Ein .NET-Programm muss lediglich in IL geparst werden. Die eigentliche Kompilierung erfolgt zur Laufzeit über JIT. Allerdings dauert das Kompilieren von 300 Quelldateien im C # -Code auch einige Zeit.
gbjbaanb
7

Es ist nicht klar, was Sie damit meinen, wenn Sie die Datei nach Funktionen und Datenelementen durchsuchen. Die meisten IDEs bieten jedoch Tools zum Durchsuchen der Klasse und Anzeigen von Klassendatenelementen.

Zum Beispiel: Visual Studio hat Class Viewund Object browserbietet Ihre gewünschte Funktionalität. Wie in den folgenden Screenshots.

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

EL Yusubov
quelle
4

Ein zusätzlicher Nachteil von Header-Dateien ist, dass das Programm von der Reihenfolge ihrer Aufnahme abhängt und der Programmierer zusätzliche Schritte unternehmen muss, um die Korrektheit sicherzustellen.

Zusammen mit dem Makrosystem von C (das von C ++ geerbt wird) führt dies zu vielen Fallstricken und verwirrenden Situationen.

Zur Veranschaulichung: Wenn ein Header ein Symbol mithilfe von Makros definiert und ein anderer Header das Symbol auf andere Weise verwendet, z. B. als Name für eine Funktion, hat die Reihenfolge der Einbeziehung großen Einfluss auf das Endergebnis. Es gibt viele solcher Beispiele.

jcora
quelle
2

Ich mochte Header-Dateien immer, da sie eine Art Schnittstelle zur Implementierung bieten, zusammen mit einigen zusätzlichen Informationen wie Klassenvariablen, die alle in einer einfach anzuzeigenden Datei enthalten sind.

Ich sehe eine Menge C # -Code (für den nicht zwei Dateien pro Klasse erforderlich sind), der mit zwei Dateien pro Klasse geschrieben wurde - eine mit der tatsächlichen Klassenimplementierung und eine mit einer Schnittstelle. Dieser Entwurf ist gut zum Verspotten geeignet (in einigen Systemen unerlässlich) und hilft beim Definieren der Dokumentation der Klasse, ohne dass die kompilierten Metadaten mit der IDE angezeigt werden müssen. Ich würde so weit gehen, um seine gute Praxis zu sagen.

Daher ist es eine gute Sache, wenn C / C ++ eine Art Äquivalent einer Schnittstelle in Header-Dateien vorschreibt.

Ich weiß, dass es Befürworter anderer Systeme gibt, die sie aus Gründen nicht mögen, darunter "es ist schwieriger, Code einfach zu hacken, wenn man Dinge in zwei Dateien packen muss", aber ich bin der Meinung, dass das Hacken von Code sowieso keine gute Praxis ist , und sobald Sie anfangen, Code mit ein wenig mehr Nachdenken zu schreiben / zu entwerfen, werden Sie selbstverständlich Header / Interfaces definieren.

gbjbaanb
quelle
Schnittstelle und Implementierung in verschiedenen Dateien? Wenn Sie dies beabsichtigt benötigen, ist dies immer noch weit verbreitet, um die Schnittstelle (oder abstrakte Klasse) in Sprachen wie Java / C # in einer Datei zu definieren und die Implementierung (en) in anderen Dateien auszublenden. Dies erfordert keine alten und kniffligen Header-Dateien.
Petter Nordlander
wie? habe ich nicht gesagt - du definierst die Schnittstelle und die Klassenimplementierung in 2 Dateien.
gbjbaanb
2
Nun, das erfordert keine Header-Dateien und das macht Header-Dateien nicht besser. Ja, sie werden in älteren Sprachen benötigt, aber als Konzept werden sie nicht benötigt, um die Schnittstelle / Implementierung in verschiedene Dateien aufzuteilen (tatsächlich machen sie es noch schlimmer).
Petter Nordlander
@Petter Ich denke, Sie verstehen das falsch, konzeptionell sind sie nicht anders - ein Header existiert, um eine Schnittstelle in der C-Sprache bereitzustellen, und obwohl Sie Recht haben, können Sie die 2 kombinieren (wie viele mit Nur-Header-C-Code tun), es ist kein Best Practice, die ich gesehen habe, wurde an vielen Orten befolgt. Alle C # -Entwickler, die ich gesehen habe, haben beispielsweise die Schnittstelle von der Klasse getrennt. Es ist eine gute Praxis, dies zu tun, da Sie dann die Schnittstellendatei ohne Verschmutzung in mehrere andere Dateien aufnehmen können. Wenn Sie der Meinung sind, dass diese Aufteilung eine bewährte Methode ist, können Sie sie in C mithilfe von Header-Dateien erreichen.
gbjbaanb
Wenn Header tatsächlich nützlich wären, um Schnittstelle und Implementierung zu trennen, wären Dinge wie PImpl nicht erforderlich, um private Komponenten auszublenden und Endbenutzer von einer Neukompilierung zu entkoppeln. Stattdessen bekommen wir das Schlimmste aus beiden Welten. Kopfzeilen täuschen eine schnell verworfene Fassade der Trennung vor und eröffnen eine Vielzahl von Fallstricken, die einen ausführlichen Code erfordern, wenn Sie sie umgehen müssen.
Underscore_d
1

Eigentlich würde ich sagen, dass Header-Dateien nicht großartig sind, weil sie die Oberfläche und die Implementierung verschleiern. Die Absicht bei der Programmierung im Allgemeinen und insbesondere bei OOP ist es, eine definierte Schnittstelle zu haben und die Implementierungsdetails auszublenden. In einer C ++ - Headerdatei werden jedoch sowohl die Methoden, die Vererbung und die öffentlichen Mitglieder (Schnittstelle) als auch die privaten Methoden und die privaten Mitglieder angezeigt (ein Teil der Implementierung). Ganz zu schweigen davon, dass in einigen Fällen Code oder Konstruktoren in die Header-Datei eingefügt werden und einige Bibliotheken Vorlagencode in die Header-Dateien einfügen, was die Implementierung wirklich mit der Schnittstelle vermischt.

Ich glaube, die ursprüngliche Absicht war es, es dem Code zu ermöglichen, andere Bibliotheken, Objekte usw. zu verwenden, ohne den gesamten Inhalt des Skripts importieren zu müssen. Alles was Sie brauchen, ist der Header zum Kompilieren und Verknüpfen. Das spart Zeit und Zyklen. In diesem Fall ist es eine anständige Idee, aber es ist nur eine Möglichkeit, diese Probleme zu lösen.

Was das Durchsuchen der Programmstruktur angeht, bieten die meisten IDEs diese Möglichkeit, und es gibt eine Reihe von Tools, mit denen Sie die Benutzeroberflächen austauschen, Code analysieren, dekompilieren usw. können, um zu sehen, was unter der Decke passiert.

Warum implementieren andere Sprachen nicht die gleiche Funktion? Nun, weil andere Sprachen von anderen Menschen kommen und diese Designer / Schöpfer eine andere Vorstellung davon haben, wie die Dinge funktionieren sollten.

Die beste Antwort ist, sich an die Aufgaben zu halten, die Sie erledigen müssen, und Sie glücklich zu machen.

Maurice Reeves
quelle
0

In vielen Programmiersprachen kann Code in einer Programmeinheit, wenn ein Programm in mehrere Kompilierungseinheiten unterteilt ist, nur dann Dinge verwenden, die in einer anderen definiert sind, wenn der Compiler über Mittel verfügt, um zu wissen, was diese Dinge sind. Anstatt zu verlangen, dass ein Compiler, der eine Kompilierungseinheit verarbeitet, den gesamten Text jeder Kompilierungseinheit überprüft, die alles definiert, was in der vorliegenden Einheit verwendet wird, ist es am besten, dass der Compiler während der Verarbeitung jeder Einheit den vollständigen Text der zu kompilierenden Einheit empfängt mit ein paar Informationen von allen anderen.

In C ist der Programmierer dafür verantwortlich, zwei Dateien für jede Einheit zu erstellen - eine mit Informationen, die nur beim Kompilieren dieser Einheit benötigt werden, und eine mit Informationen, die auch beim Kompilieren anderer Einheiten benötigt werden. Dies ist ein ziemlich unangenehmes, hackiges Design, aber es vermeidet die Notwendigkeit, einen Sprachstandard festzulegen, der angibt, wie Compiler Zwischendateien generieren und verarbeiten sollen. Viele andere Sprachen verwenden einen anderen Ansatz, bei dem ein Compiler aus jeder Quelldatei eine Zwischendatei generiert, die alles in dieser Quelldatei beschreibt, was für externen Code zugänglich sein soll. Dieser Ansatz vermeidet die Notwendigkeit, doppelte Informationen in zwei Quelldateien zu haben, erfordert jedoch, dass eine Kompilierungsplattform die Dateisemantik auf eine Weise definiert hat, die für C nicht erforderlich ist.

Superkatze
quelle