Was soll in eine .h-Datei gehen?

93

Wenn Sie Ihren Code in mehrere Dateien aufteilen, was genau sollte in eine .h-Datei und was in eine .cpp-Datei passen?

Enrico Tuvera Jr.
quelle
Verwandte Frage: stackoverflow.com/questions/333889/…
Spoike
7
Dies ist ein reines Stilproblem, aber ich glaube, dass C ++ - Deklarationen in einer .hppDatei gespeichert werden, während C-Deklarationen in einer .hDatei gespeichert werden. Dies ist sehr hilfreich beim Mischen von C- und C ++ - Code (z. B. Legacy-Module in C).
Thomas Matthews
@ ThomasMatthews macht Sinn. Wird diese Praxis oft angewendet?
ty
@lightningleaf: Ja, die Praxis wird häufig verwendet, insbesondere beim Mischen von C ++ - und C-Sprachen.
Thomas Matthews

Antworten:

113

Header-Dateien ( .h) sollen die Informationen bereitstellen, die in mehreren Dateien benötigt werden. Dinge wie Klassendeklarationen, Funktionsprototypen und Aufzählungen werden normalerweise in Header-Dateien gespeichert. Mit einem Wort "Definitionen".

Codedateien ( .cpp) sollen die Implementierungsinformationen bereitstellen, die nur in einer Datei bekannt sein müssen. Im Allgemeinen gehören Funktionskörper und interne Variablen, auf die andere Module niemals zugreifen sollten / werden, zu den .cppDateien. Mit einem Wort "Implementierungen".

Die einfachste Frage, die Sie sich stellen müssen, um festzustellen, was wohin gehört, lautet: "Wenn ich dies ändere, muss ich dann den Code in anderen Dateien ändern, damit die Dinge wieder kompiliert werden?" Wenn die Antwort "Ja" lautet, gehört sie wahrscheinlich in die Header-Datei. Wenn die Antwort "Nein" lautet, gehört sie wahrscheinlich in die Codedatei.

Bernstein
quelle
4
Außer private Klassendaten müssen in den Header gehen. Vorlagen müssen vollständig als Header definiert sein (es sei denn, Sie verwenden einen der wenigen unterstützten Compiler export). Der einzige Weg um # 1 herum ist PIMPL. # 2 wäre möglich, wenn exportes unterstützt würde und möglicherweise mit c ++ 0x und externVorlagen möglich wäre. IMO, Header-Dateien in C ++ verlieren viel von ihrer Nützlichkeit.
KitsuneYMG
23
Alles gut, aber mit ungenauer Terminologie. Mit einem Wort, "Deklarationen" - der Begriff "Definition" ist gleichbedeutend mit "Implementierung". In einem Header sollten nur deklarativer Code, Inline-Code, Makrodefinitionen und Vorlagencode enthalten sein. dh nichts, was Code oder Daten instanziiert.
Clifford
8
Ich muss Clifford zustimmen. Sie verwenden die Begriffe Deklaration und Definition eher locker und etwas austauschbar. Aber sie haben genaue Bedeutungen in C ++. Beispiele: Eine Klassendeklaration führt einen Namen einer Klasse ein, sagt aber nicht, was darin enthalten ist. Eine Klassendefinition listet alle Mitglieder- und Freundesfunktionen auf. Beide können problemlos in Header-Dateien abgelegt werden. Was Sie „Funktionsprototyp“ nennen , ist eine Funktion Erklärung . Aber eine Funktion Definition ist , dass , was die Funktion des Code enthält und sollte in eine CPP-Datei platziert werden - es sei denn , es ist inline oder (Teil) einer Vorlage.
Sellibitze
5
Sie haben genaue Bedeutungen in C ++, sie haben keine genauen Bedeutungen in Englisch. Meine Antwort wurde in letzterem geschrieben.
Amber
54

Tatsache ist, dass dies in C ++ etwas komplizierter ist als die C-Header- / Quellorganisation.

Was sieht der Compiler?

Der Compiler sieht eine große Quelldatei (.cpp), deren Header ordnungsgemäß enthalten sind. Die Quelldatei ist die Kompilierungseinheit, die in eine Objektdatei kompiliert wird.

Warum sind Header notwendig?

Weil eine Kompilierungseinheit möglicherweise Informationen über eine Implementierung in einer anderen Kompilierungseinheit benötigt. So kann man beispielsweise die Implementierung einer Funktion in eine Quelle schreiben und die Deklaration dieser Funktion in eine andere Quelle schreiben, die sie verwenden muss.

In diesem Fall gibt es zwei Kopien derselben Informationen. Welches ist böse ...

Die Lösung besteht darin, einige Details zu teilen. Während die Implementierung in der Quelle verbleiben sollte, muss möglicherweise die Deklaration gemeinsamer Symbole wie Funktionen oder die Definition von Strukturen, Klassen, Aufzählungen usw. gemeinsam genutzt werden.

Überschriften werden verwendet, um diese freigegebenen Details zu platzieren.

Verschieben Sie in den Header die Deklarationen darüber, was von mehreren Quellen gemeinsam genutzt werden muss

Nichts mehr?

In C ++ gibt es einige andere Dinge, die in den Header eingefügt werden könnten, da sie ebenfalls gemeinsam genutzt werden müssen:

  • Inline-Code
  • Vorlagen
  • Konstanten (normalerweise diejenigen, die Sie in Schaltern verwenden möchten ...)

Gehen Sie in den Header ALLES, was freigegeben werden muss, einschließlich freigegebener Implementierungen

Bedeutet das dann, dass sich Quellen in den Headern befinden könnten?

Ja. Tatsächlich gibt es viele verschiedene Dinge, die sich in einem "Header" befinden könnten (dh zwischen Quellen geteilt werden).

  • Erklärungen weiterleiten
  • Deklarationen / Definition von Funktionen / Strukturen / Klassen / Vorlagen
  • Implementierung von Inline- und Vorlagencode

Es wird kompliziert und in einigen Fällen (zirkuläre Abhängigkeiten zwischen Symbolen) unmöglich, es in einem Header zu halten.

Überschriften können in drei Teile unterteilt werden

Dies bedeutet, dass Sie im Extremfall Folgendes haben könnten:

  • ein Forward-Deklarationsheader
  • ein Deklarations- / Definitionsheader
  • ein Implementierungsheader
  • eine Implementierungsquelle

Stellen wir uns vor, wir haben ein MyObject mit Vorlagen. Wir könnten haben:

// - - - - MyObject_forward.hpp - - - - 
// This header is included by the code which need to know MyObject
// does exist, but nothing more.
template<typename T>
class MyObject ;

.

// - - - - MyObject_declaration.hpp - - - - 
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>

template<typename T>
class MyObject
{
   public :
      MyObject() ;
   // Etc.
} ;

void doSomething() ;

.

// - - - - MyObject_implementation.hpp - - - - 
// This header is included by the code which need to see
// the implementation of the methods/functions of MyObject,
// but nothing more.
#include <MyObject_declaration.hpp>

template<typename T>
MyObject<T>::MyObject()
{
   doSomething() ;
}

// etc.

.

// - - - - MyObject_source.cpp - - - - 
// This source will have implementation that does not need to
// be shared, which, for templated code, usually means nothing...
#include <MyObject_implementation.hpp>

void doSomething()
{
   // etc.
} ;

// etc.

Beeindruckend!

Im "wirklichen Leben" ist es normalerweise weniger kompliziert. Der meiste Code hat nur eine einfache Header- / Quellorganisation mit etwas Inline-Code in der Quelle.

In anderen Fällen (Vorlagenobjekte, die sich kennen) musste ich für jedes Objekt separate Deklarations- und Implementierungsheader haben, wobei eine leere Quelle diese Header enthielt, damit ich einige Kompilierungsfehler erkennen konnte.

Ein weiterer Grund für die Aufteilung von Headern in separate Header könnte darin bestehen, die Kompilierung zu beschleunigen, die Anzahl der analysierten Symbole auf das erforderliche Maß zu beschränken und eine unnötige Neukompilierung einer Quelle zu vermeiden, die sich nur um die Vorwärtsdeklaration kümmert, wenn sich eine Implementierung einer Inline-Methode ändert.

Fazit

Sie sollten Ihre Code-Organisation so einfach wie möglich und so modular wie möglich gestalten. Fügen Sie so viel wie möglich in die Quelldatei ein. Stellen Sie in Headern nur fest, was freigegeben werden muss.

Aber an dem Tag, an dem Sie kreisförmige Abhängigkeiten zwischen Vorlagenobjekten haben, wundern Sie sich nicht, wenn Ihre Codeorganisation etwas "interessanter" wird als die einfache Header- / Quellorganisation ...

^ _ ^

paercebal
quelle
17

Zusätzlich zu allen anderen Antworten werde ich Ihnen sagen, was Sie NICHT in eine Header-Datei einfügen: Die
usingDeklaration (das häufigste Wesen using namespace std;) sollte nicht in einer Header-Datei erscheinen, da sie den Namespace der Quelldatei verschmutzt, in der sie enthalten ist .

Adrien Plisson
quelle
+1 mit einer Einschränkung, die Sie verwenden können, solange es sich um einen detaillierten Namespace (oder einen anonymen Namespace) handelt. Aber ja, niemals verwenden using, um Dinge in einem Header in den globalen Namespace zu bringen.
KitsuneYMG
+1 Dieser ist viel einfacher zu beantworten. :) Außerdem sollten Header-Dateien keine anonymen Namespaces enthalten .
Sellibitze
Es ist in Ordnung, dass Header-Dateien anonyme Namespaces enthalten, solange Sie verstehen, was dies bedeutet, dh, dass jede Übersetzungseinheit eine andere Kopie des von Ihnen definierten Namespace hat. Inline-Funktionen in anonymen Namespaces werden in C ++ für Fälle empfohlen, in denen Sie sie static inlinein C99 verwenden würden, da dies damit zu tun hat, was passiert, wenn Sie interne Verknüpfungen mit Vorlagen kombinieren. Mit Anon-Namespaces können Sie Funktionen "ausblenden" und gleichzeitig die externe Verknüpfung beibehalten.
Steve Jessop
Steve, was du geschrieben hast, hat mich nicht überzeugt. Bitte wählen Sie ein konkretes Beispiel aus, bei dem ein anon-Namespace in einer Header-Datei Ihrer Meinung nach absolut sinnvoll ist.
Sellibitze
6

Was zu nichts kompiliert wird (kein binärer Footprint), wird in die Header-Datei aufgenommen.

Variablen werden nicht zu nichts kompiliert, Typdeklarationen jedoch (weil sie nur beschreiben, wie sich Variablen verhalten).

Funktionen nicht, aber Inline-Funktionen (oder Makros), weil sie nur dort Code erzeugen, wo sie aufgerufen werden.

Vorlagen sind kein Code, sondern nur ein Rezept zum Erstellen von Code. so gehen sie auch in h-Dateien.

Pavel Radzivilovsky
quelle
1
"Inline-Funktionen ... erzeugen Code nur dort, wo er aufgerufen wird". Das ist nicht wahr. Inline-Funktionen können an Anrufstellen inline sein oder nicht, aber selbst wenn sie inline sind, existiert der reale Funktionskörper genauso wie bei einer Nicht-Inline-Funktion. Der Grund, warum es in Ordnung ist, Inline-Funktionen in Headern zu haben, hat nichts damit zu tun, ob sie Code generieren. Dies liegt daran, dass Inline-Funktionen nicht die eine Definitionsregel auslösen. Im Gegensatz zu Nicht-Inline-Funktionen gibt es also keine Probleme, zwei verschiedene Übersetzungseinheiten miteinander zu verknüpfen die beide den Header enthalten haben.
Steve Jessop
3

Im Allgemeinen fügen Sie Deklarationen in die Header-Datei und Definitionen in die Implementierungsdatei (.cpp) ein. Eine Ausnahme bilden Vorlagen, bei denen die Definition auch im Header enthalten sein muss.

Diese und ähnliche Fragen wurden häufig in SO gestellt - siehe Warum haben Header- und CPP-Dateien in C ++? und C ++ - Header-Dateien, z. B. Codetrennung .

Gemeinschaft
quelle
Natürlich können Sie auch Klasse setzen Definitionen in Header - Dateien. Sie müssen nicht einmal Vorlagen sein.
Sellibitze
1

Ihre Klassen- und Funktionsdeklarationen sowie die Dokumentation und die Definitionen für Inline-Funktionen / Methoden (obwohl einige es vorziehen, sie in separaten INL-Dateien abzulegen).

Alexander Gessler
quelle
1

Hauptsächlich Header-Dateien enthalten Klassenskelett oder Deklaration (ändert sich nicht häufig)

und cpp-Datei enthält Klassenimplementierung (Änderungen häufig).

Ashish
quelle
5
Bitte verwenden Sie keine nicht standardmäßige Terminologie. Was ist "Klassenskelett", was ist "Klassenimplementierung"? Was Sie als Deklaration im Kontext von Klassen bezeichnen, enthält wahrscheinlich auch Klassendefinitionen.
Sellibitze
0

Die Header-Datei (.h) sollte für Deklarationen von Klassen, Strukturen und deren Methoden, Prototypen usw. sein. Die Implementierung dieser Objekte erfolgt in cpp.

in .h

    class Foo {
    int j;

    Foo();
    Foo(int)
    void DoSomething();
}
Jose
quelle
0

Ich würde erwarten zu sehen:

  • Erklärungen
  • Bemerkungen
  • Definitionen inline markiert
  • Vorlagen

Die wirkliche Antwort ist jedoch, was man nicht eingeben sollte:

  • Definitionen (kann dazu führen, dass Dinge mehrfach definiert werden)
  • Die Verwendung von Deklarationen / Anweisungen (zwingt sie jedem auf, einschließlich Ihrer Kopfzeile, kann zu Namenskonflikten führen.)
jk.
quelle
1
Sie können natürlich auch Klassendefinitionen in Header-Dateien einfügen. Eine Klassendeklaration sagt nichts über ihre Mitglieder aus.
Sellibitze
0

Der Header Definiert etwas, sagt aber nichts über die Implementierung aus. (Ausgenommen Vorlagen in diesem "Metafore".

Nachdem dies gesagt wurde, müssen Sie "Definitionen" in Untergruppen unterteilen. In diesem Fall gibt es zwei Arten von Definitionen.

  • Sie definieren das "Layout" Ihrer Struktur und sagen nur so viel, wie von den umgebenden Nutzungsgruppen benötigt wird.
  • Die Definitionen einer Variablen, Funktion und Klasse.

Jetzt spreche ich natürlich über die erste Untergruppe.

Der Header dient dazu, das Layout Ihrer Struktur zu definieren, damit der Rest der Software die Implementierung verwenden kann. Vielleicht möchten Sie es als "Abstraktion" Ihrer Implementierung sehen, was vage gesagt wird, aber ich denke, es passt in diesem Fall ganz gut.

Wie bereits in früheren Postern erwähnt, haben Sie private und öffentliche Nutzungsbereiche und deren Überschriften deklariert. Dazu gehören auch private und öffentliche Variablen. Nun, ich möchte hier nicht auf das Design des Codes eingehen, aber Sie möchten vielleicht überlegen, was Sie in Ihre Header einfügen, da dies die Ebene zwischen dem Endbenutzer und der Implementierung ist.

Filip Ekberg
quelle
0
  • Header-Dateien - sollten sich während der Entwicklung nicht zu oft ändern -> Sie sollten überlegen und sie sofort schreiben (im Idealfall)
  • Quelldateien - Änderungen während der Implementierung
nothrow
quelle
Dies ist eine Praxis. Bei einigen kleineren Projekten ist dies möglicherweise der richtige Weg. Sie können jedoch versuchen, Funktionen und ihre Prototypen (in Header-Dateien) zu verwerfen, anstatt ihre Signatur zu ändern oder sie zu entfernen. Zumindest bis zum Ändern der Hauptnummer. Wie wenn 1.9.2 auf 2.0.0 Beta gestoßen wird.
TamusJRoyce
0

Header (.h)

  • Makros und Includes, die für die Schnittstellen benötigt werden (so wenige wie möglich)
  • Die Deklaration der Funktionen und Klassen
  • Dokumentation der Schnittstelle
  • Erklärung der Inline-Funktionen / Methoden, falls vorhanden
  • extern zu globalen Variablen (falls vorhanden)

Körper (.cpp)

  • Restliche Makros und Includes
  • Fügen Sie den Header des Moduls ein
  • Definition von Funktionen und Methoden
  • Globale Variablen (falls vorhanden)

Als Faustregel setzen Sie den "freigegebenen" Teil des Moduls auf die .h (den Teil, den andere Module sehen müssen) und den "nicht freigegebenen" Teil auf die .cpp

PD: Ja, ich habe globale Variablen aufgenommen. Ich habe sie einige Male verwendet und es ist wichtig, sie nicht in den Headern zu definieren, sonst erhalten Sie viele Module, von denen jedes seine eigene Variable definiert.

EDIT: Geändert nach dem Kommentar von David

Khelben
quelle
Als Faustregel sollten möglichst wenige Includes in der .h-Datei enthalten sein, und die .cpp-Datei sollte alle benötigten Header enthalten. Das verkürzt die Kompilierungszeiten und verschmutzt keine Namespaces.
David Thornley