Was soll und was soll nicht in einer Header-Datei sein? [geschlossen]

71

Welche Dinge sollten auf keinen Fall in einer Header-Datei enthalten sein?

Wenn ich zum Beispiel mit einem dokumentierten Industriestandardformat arbeite, das viele Konstanten enthält, ist es eine gute Praxis, diese in einer Headerdatei zu definieren (wenn ich einen Parser für dieses Format schreibe)?

Welche Funktionen sollen in die Header-Datei gehen?
Welche Funktionen sollten nicht?

Moshe Magnes
quelle
1
Kurz und schmerzlos: Definitionen und Deklarationen, die in mehr als einem Modul benötigt werden.
ott--
21
Das Markieren dieser Frage als "zu breit" und Schließen ist eine absolute Schande, wenn es um Mäßigung geht. Diese Frage fragt genau , wonach ich suche - die Frage ist gut formuliert und stellt eine sehr klare Frage: Was sind die besten Praktiken? Wenn dies für Softwareengineering "zu umfassend" ist, können wir das ganze Forum auch einfach schließen.
Gewure
TL; DR. Für C ++ wird in der vierten Ausgabe von "The C ++ Programming Language", geschrieben von Bjarne Stroustrup (seinem Schöpfer), in Abschnitt 15.2.2 beschrieben, was ein Header enthalten sollte und was nicht. Ich weiß, dass Sie die Frage mit C markiert haben, aber einige der Ratschläge gelten auch. Ich denke, das ist eine gute Frage ...
Horro

Antworten:

57

Was in Überschriften zu setzen:

  • Der minimale Satz von #includeAnweisungen, die erforderlich sind, um den Header kompilierbar zu machen, wenn der Header in einer Quelldatei enthalten ist.
  • Präprozessorsymboldefinitionen von Dingen, die gemeinsam genutzt werden müssen und die nur über den Präprozessor ausgeführt werden können. Auch in C werden Präprozessorsymbole am besten auf ein Minimum beschränkt.
  • Weiterleitende Deklarationen von Strukturen, die erforderlich sind, um die Strukturdefinitionen, Funktionsprototypen und globalen Variablendeklarationen im Hauptteil des Headers kompilierbar zu machen.
  • Definitionen von Datenstrukturen und Aufzählungen, die von mehreren Quelldateien gemeinsam genutzt werden.
  • Deklarationen für Funktionen und Variablen, deren Definitionen für den Linker sichtbar sind.
  • Inline-Funktionsdefinitionen, aber Vorsicht hier.

Was nicht in einen Header gehört:

  • Unentgeltliche #includeWeisungen. Diese unentgeltlichen Includes verursachen eine Neukompilierung von Dingen, die nicht neu kompiliert werden müssen, und können es manchmal so machen, dass ein System nicht kompiliert werden kann. Keine #includeDatei in einem Header, wenn der Header selbst diese andere Headerdatei nicht benötigt.
  • Präprozessorsymbole, deren Absicht durch irgendeinen Mechanismus, irgendeinen anderen Mechanismus als den Präprozessor erreicht werden könnte.
  • Viele, viele Strukturdefinitionen. Teilen Sie diese in separate Header auf.
  • Inline-Definitionen von Funktionen, für die eine zusätzliche Funktion erforderlich #includeist, die geändert werden kann oder die zu groß sind. Diese Inline-Funktionen sollten wenig oder gar keinen Fan-Out haben, und wenn sie Fan-Out haben, sollten sie auf die im Header definierten Elemente lokalisiert werden.

Was macht die minimale Menge von #includeAussagen aus?

Dies stellt sich als nicht triviale Frage heraus. Eine TL; DR-Definition: Eine Header-Datei muss die Header-Dateien enthalten, die direkt jeden der direkt in der betreffenden Header-Datei verwendeten Typen definieren oder die direkt jede der in der betreffenden Header-Datei verwendeten Funktionen deklarieren, darf jedoch nichts anderes enthalten. Ein Zeiger- oder C ++ - Referenztyp gilt nicht als direkte Verwendung. Vorwärtsreferenzen werden bevorzugt.

Es gibt einen Platz für eine unbegründete #includeAnweisung, und dies erfolgt in einem automatisierten Test. Für jede Header-Datei in einem Softwarepaket erstelle und kompiliere ich automatisch Folgendes:

#include "path/to/random/header_under_test"
int main () { return 0; }

Die Kompilierung sollte sauber sein (dh frei von Warnungen oder Fehlern). Warnungen oder Fehler in Bezug auf unvollständige oder unbekannte Typen bedeuten, dass in der zu testenden Header-Datei einige #includeAnweisungen und / oder Vorwärtserklärungen fehlen. Gut zu beachten: Nur weil der Test bestanden wurde, bedeutet dies nicht, dass der #includeRichtliniensatz ausreicht, geschweige denn minimal.

David Hammen
quelle
Wenn ich also eine Bibliothek habe, die eine Struktur mit dem Namen A definiert, und diese Bibliothek mit dem Namen B diese Struktur verwendet und Bibliothek B von Programm C verwendet wird, sollte ich die Header-Datei von Bibliothek A in den Hauptheader von Bibliothek B aufnehmen oder sollte Ich erkläre es einfach weiter? Bibliothek A wird kompiliert und während der Kompilierung mit Bibliothek B verknüpft.
MarcusJ
@MarcusJ - Das allererste, was ich unter " Was nicht in einen Header gehört" aufgelistet habe, waren unbegründete # include-Anweisungen. Wenn die Header-Datei B nicht von den Definitionen in der Header-Datei A abhängt, #schließen Sie die Header-Datei A nicht in die Header-Datei B. In einer Header-Datei können keine Abhängigkeiten von Drittanbietern oder Build-Anweisungen angegeben werden. Diese befinden sich an einem anderen Ort, beispielsweise in einer Readme-Datei der obersten Ebene.
David Hammen
1
@MarcusJ - Ich habe meine Antwort aktualisiert, um Ihre Frage zu beantworten. Beachten Sie, dass Ihre Frage nicht einmal beantwortet wurde. Ich werde mit ein paar Extremen illustrieren. Fall 1: Die einzige Stelle, an der Bibliothek B die Funktionalität von Bibliothek A direkt verwendet, sind die Quelldateien von Bibliothek B. Fall 2: Bibliothek B ist eine Erweiterung der Funktionalität in Bibliothek A, wobei die Header-Datei (en) für Bibliothek B direkt die in Bibliothek A definierten Typen und / oder Funktionen verwenden. In Fall 1 gibt es keinen Grund, Bibliothek A in verfügbar zu machen die Überschrift (en) für Bibliothek B. In Fall 2 ist diese Belichtung so ziemlich obligatorisch.
David Hammen
Ja, es ist Fall 2, mein Kommentar hat leider die Tatsache übersprungen, dass es Typen verwendet, die in Bibliothek A in den Kopfzeilen von Bibliothek B deklariert wurden. Ich dachte, ich könnte es weiterleiten, aber ich glaube nicht, dass das funktionieren wird. Danke für das Update.
MarcusJ
Ist das Hinzufügen von Konstanten zu einer Header-Datei ein großes Nein-Nein?
mding5692
15

Zusätzlich zu dem, was bereits gesagt wurde.

H-Dateien sollten immer enthalten:

  • Quelltextdokumentation !!! Zumindest was ist der Zweck der verschiedenen Parameter und Rückgabewerte der Funktionen.
  • Header Guards, #ifndef MYHEADER_H #def MYHEADER_H ... #endif

H-Dateien sollten niemals enthalten:

  • Beliebige Form der Datenzuordnung.
  • Funktionsdefinitionen. Inline-Funktionen können in einigen Fällen eine seltene Ausnahme sein.
  • Alles beschriftet static.
  • Typedefs, #defines oder Konstanten, die für den Rest der Anwendung keine Relevanz haben.

(Ich würde auch sagen, dass es nie einen Grund gibt, nicht konstante globale / externe Variablen zu verwenden, aber das ist eine Diskussion für einen anderen Beitrag.)


quelle
1
Ich bin mit allem einverstanden, außer dem, was Sie hervorgehoben haben. Wenn Sie eine Bibliothek erstellen, sollten Sie oder die Benutzer Ihrer Bibliothek dokumentieren. Für ein firmeninternes Projekt sollten Sie Ihre Überschriften nicht mit Dokumentation überfrachten müssen, wenn Sie gute, selbsterklärende Variablen- und Funktionsnamen verwenden.
8.
5
@martiert bin ich auch von der "Lass den Code für sich sprechen" Schule. Sie sollten Ihre Funktionen jedoch zumindest immer dokumentieren, auch wenn Sie nur von Ihnen selbst verwendet werden. Von besonderem Interesse sind: Wenn die Funktion eine Fehlerbehandlung aufweist, welche Fehlercodes werden zurückgegeben und unter welchen Bedingungen schlägt sie fehl? Was passiert mit Parametern (Puffern, Zeigern usw.), wenn die Funktion fehlschlägt? Eine andere Sache, die sehr relevant ist, ist die Frage, ob die Zeigerparameter dem Aufrufer etwas zurückgeben, dh ob sie zugewiesenen Speicher erwarten. ->
1
Dem Aufrufer sollte klar sein, welche Fehlerbehandlung innerhalb der Funktion erfolgt und welche nicht. Wenn die Funktion einen zugewiesenen Puffer erwartet, überlässt sie die Out-of-Bound-Prüfungen höchstwahrscheinlich auch dem Aufrufer. Wenn die Funktion auf eine andere auszuführende Funktion angewiesen ist, muss dies dokumentiert werden (dh link_list_init () vor link_list_add () ausführen). Und schließlich, wenn die Funktion einen "Nebeneffekt" wie das Erstellen von Dateien, Threads, Timern oder was auch immer hat, sollte dies in der Dokumentation angegeben werden. ->
1
Vielleicht ist "Quelltextdokumentation" hier zu umfangreich, das gehört wirklich zum Quelltext. "Usage documentation" mit Input und Output, Vor- und Nachbedingungen und Nebenwirkungen sollte auf jeden Fall da sein, nicht in epischer, sondern in kurzer Form.
Sichern Sie sich den
2
Ein bisschen verspätet, aber +1 für die Dokumentation. Warum gibt es diese Klasse? Der Code spricht nicht für sich. Was macht diese Funktion? RTFC (lesen Sie die feine .cpp-Datei) ist ein obszönes Akronym mit vier Buchstaben. Man sollte niemals RTFC zum Verständnis haben. Der Prototyp in der Kopfzeile sollte in einem extrahierbaren Kommentar (z. B. doxygen) zusammenfassen, was die Argumente sind und was die Funktion tut. Warum existiert dieses Datenelement, was enthält es und ist der Wert in Metern, Fuß oder Furlong? Auch das ist ein anderes Thema für (extrahierbare) Kommentare.
David Hammen
4

Ich würde wahrscheinlich nie nie sagen, aber Anweisungen, die Daten und Code generieren, während sie analysiert werden, sollten nicht in einer .h-Datei enthalten sein.

Makros, Inline-Funktionen und Vorlagen sehen möglicherweise wie Daten oder Code aus, generieren jedoch keinen Code, wenn sie analysiert werden, sondern wenn sie verwendet werden. Diese Elemente müssen häufig in mehr als einer .c- oder .cpp-Datei verwendet werden, sodass sie in die .h-Datei gehören.

Meiner Ansicht nach sollte eine Header-Datei die minimale praktische Schnittstelle zu einer entsprechenden .c- oder .cpp-Datei haben. Die Schnittstelle kann #defines, class, typedef, Strukturdefinitionen, Funktionsprototypen und weniger bevorzugte externe Definitionen für globale Variablen enthalten. Wenn eine Deklaration jedoch nur in einer Quelldatei verwendet wird, sollte sie wahrscheinlich aus der .h-Datei ausgeschlossen und stattdessen in der Quelldatei enthalten sein.

Einige mögen anderer Meinung sein, aber mein persönliches Kriterium für .h-Dateien ist, dass sie alle anderen .h-Dateien enthalten, die sie kompilieren müssen. In einigen Fällen kann dies eine Vielzahl von Dateien sein. Daher verfügen wir über einige effektive Methoden, um externe Abhängigkeiten wie Forward-Deklarationen für Klassen zu reduzieren, mit denen wir Zeiger auf Objekte einer Klasse verwenden können, ohne einen möglicherweise großen Baum von Include-Dateien einzuschließen.

DeveloperDon
quelle
3

Die Header-Datei sollte die folgende Organisation haben:

  • Typ- und Konstantendefinitionen
  • externe Objektdeklarationen
  • externe Funktionsdeklarationen

Header-Dateien sollten niemals Objektdefinitionen, sondern nur Typdefinitionen und Objektdeklarationen enthalten.

theD
quelle
Was ist mit Inline-Funktionsdefinitionen?
Kos
Wenn es sich bei der Inline-Funktion um eine „Hilfsfunktion“ handelt, die nur in einem C-Modul verwendet wird, fügen Sie sie nur in diese C-Datei ein. Wenn die Inline-Funktion für zwei oder mehr Module sichtbar sein muss, setzen Sie sie in die Header-Datei.
6.
Wenn die Funktion über eine Bibliotheksgrenze hinweg sichtbar sein muss, machen Sie sie nicht inline, da dies jeden zwingt, der die Bibliothek verwendet, bei jeder Änderung neu zu kompilieren.
Donal Fellows
@DonalFellows: Das ist eine Rückhandlösung. Eine bessere Regel: Fügen Sie keine Inhalte in Überschriften ein, die häufig geändert werden. Es ist nichts Falsches daran, eine kurze kleine Funktion in einen Header einzufügen, wenn die Funktion kein Fanout hat und eine klare Definition hat, die sich nur ändert, wenn sich die zugrunde liegende Datenstruktur ändert. Wenn sich die Funktionsdefinition ändert, weil sich die zugrunde liegende Strukturdefinition geändert hat, müssen Sie alles neu kompilieren, aber Sie müssen das trotzdem tun, weil sich die Strukturdefinition geändert hat.
David Hammen
0

Anweisungen, die beim Parsen Daten und Code generieren, sollten nicht in einer .hDatei enthalten sein. Aus meiner Sicht sollte eine Header-Datei nur die minimale praktische Schnittstelle zu einem entsprechenden .coder haben .cpp.

Ajay Prasad
quelle