Stellen Sie sicher, dass Header explizit in der CPP-Datei enthalten sind

9

Ich denke, es ist im Allgemeinen eine gute Praxis, #includeden Header für alle in einer CPP-Datei verwendeten Typen zu verwenden, unabhängig davon, was bereits in der HPP-Datei enthalten ist. So könnte ich #include <string>zum Beispiel sowohl in meinem HPP als auch in meinem CPP sein, obwohl ich immer noch kompilieren könnte, wenn ich es im CPP überspringen würde. Auf diese Weise muss ich mir keine Sorgen machen, ob mein HPP eine Vorwärtsdeklaration verwendet hat oder nicht.

Gibt es Tools, die diesen #includeCodierungsstil erzwingen können ? Sollte ich diesen Codierungsstil erzwingen?

Da es dem Präprozessor / Compiler egal ist, ob der #includevom HPP oder CPP stammt, erhalte ich keine Rückmeldung, wenn ich vergesse, diesem Stil zu folgen.

M. Dudley
quelle

Antworten:

7

Dies ist eine dieser Arten von Codierungsstandards, die eher "sollten" als "sollen". Der Grund ist, dass Sie so ziemlich einen C ++ - Parser schreiben müssten, um ihn durchzusetzen.

Eine sehr häufige Regel für Header-Dateien ist, dass sie für sich selbst stehen müssen. Eine Header-Datei darf nicht erfordern, dass einige andere Header-Dateien # eingeschlossen werden, bevor der betreffende Header aufgenommen wird. Dies ist eine überprüfbare Anforderung. Bei einem zufälligen Header foo.hhsollte Folgendes kompiliert und ausgeführt werden:

#include "foo.hh"
int main () {
   return 0;
}

Diese Regel hat Konsequenzen hinsichtlich der Verwendung anderer Klassen in einigen Headern. Manchmal können diese Konsequenzen vermieden werden, indem diese anderen Klassen vorwärts deklariert werden. Dies ist bei vielen Standardbibliotheksklassen nicht möglich. Es gibt keine Möglichkeit, eine Vorlageninstanziierung wie std::stringoder weiterzuleiten std::vector<SomeType>. Sie müssen #includediese STL-Header im Header verwenden, auch wenn der Typ nur als Argument für eine Funktion verwendet wird.

Ein weiteres Problem betrifft Dinge, die Sie zufällig hineinziehen. Beispiel: Beachten Sie Folgendes:

Datei foo.cc:

#include "foo.hh"
#include "bar.hh"

void Foo::Foo () : bar() { /* body elided */ }

void Foo::do_something (int item) {
   ...
   bar.add_item (item);
   ...
}

Hier barist ein Klassendatenelement Foovom Typ Bar. Sie haben hier das Richtige getan und #included bar.hh, obwohl dies in dem Header enthalten sein müsste, der die Klasse definiert Foo. Sie haben jedoch die von Bar::Bar()und verwendeten Inhalte nicht berücksichtigt Bar::add_item(int). Es gibt viele Fälle, in denen diese Aufrufe zu zusätzlichen externen Referenzen führen können.

Wenn Sie foo.omit einem Tool wie analysieren nm, wird angezeigt, dass die Funktionen in foo.ccalle Arten von Dingen aufrufen, für die Sie nicht die entsprechenden Schritte ausgeführt haben #include. Sollten Sie also #includeAnweisungen für diese zufälligen externen Referenzen hinzufügen foo.cc? Die Antwort ist absolut nicht. Das Problem ist, dass es sehr schwierig ist, die Funktionen, die zufällig aufgerufen werden, von denen zu unterscheiden, die direkt aufgerufen werden.

David Hammen
quelle
2

Sollte ich diesen Codierungsstil erzwingen?

Wahrscheinlich nicht. Meine Regel lautet: Die Aufnahme von Header-Dateien kann nicht auftragsabhängig sein.

Sie können dies ganz einfach mit der einfachen Regel überprüfen, dass die erste von xc enthaltene Datei xh ist

Kevin Cline
quelle
1
Können Sie das näher erläutern? Ich kann nicht sehen, wie das die Ordnungsunabhängigkeit wirklich bestätigen würde.
Detly
1
Es garantiert nicht wirklich die Unabhängigkeit der Bestellung - es stellt nur sicher, dass dies #include "x.h"funktioniert, ohne dass vorhergehende erforderlich sind #include. Das ist gut genug, wenn Sie nicht missbrauchen #define.
Kevin Cline
1
Oh ich verstehe. Dann immer noch eine gute Idee.
Detly
2

Wenn Sie eine Regel erzwingen müssen, dass bestimmte Header-Dateien für sich allein stehen müssen, können Sie die bereits vorhandenen Tools verwenden. Erstellen Sie ein grundlegendes Makefile, das jede Header-Datei einzeln kompiliert, aber keine Objektdatei generiert. Sie können angeben, in welchem ​​Modus die Header-Datei kompiliert werden soll (C- oder C ++ - Modus) und überprüfen, ob sie für sich allein stehen kann. Sie können davon ausgehen, dass die Ausgabe keine Fehlalarme enthält, alle erforderlichen Abhängigkeiten deklariert sind und die Ausgabe korrekt ist.

Wenn Sie eine IDE verwenden, können Sie dies möglicherweise immer noch ohne Makefile ausführen (abhängig von Ihrer IDE). Erstellen Sie einfach ein zusätzliches Projekt, fügen Sie die Header-Dateien hinzu, die Sie überprüfen möchten, und ändern Sie die Einstellungen, um es als C- oder C ++ - Datei zu kompilieren. In MSVC würden Sie beispielsweise die Einstellung "Elementtyp" unter "Konfigurationseigenschaften-> Allgemein" ändern.

Kapitän Obvlious
quelle
1

Ich glaube nicht, dass es ein solches Tool gibt, aber ich würde mich freuen, wenn eine andere Antwort mich widerlegt.

Das Problem beim Schreiben eines solchen Werkzeugs besteht darin, dass es sehr leicht ein falsches Ergebnis meldet. Daher schätze ich den Nettovorteil eines solchen Werkzeugs auf nahe Null.

Ein solches Tool kann nur funktionieren, wenn es seine Symboltabelle auf den Inhalt der von ihm verarbeiteten Header-Datei zurücksetzt. Dann tritt jedoch das Problem auf, dass die Header, die die externe API einer Bibliothek bilden, die tatsächlichen Deklarationen an delegieren interne Header.
Zum Beispiel <string>in der GCC - libc ++ Implementierung erklärt nicht alles, aber es enthält nur eine Reihe von internen Header - Dateien, die die eigentlichen Erklärungen enthalten. Wenn das Tool seine Symboltabelle auf das zurücksetzt, was von <string>selbst deklariert wurde, wäre das nichts.
Sie könnten das Tool zwischen #include ""und unterscheiden lassen #include <>, aber das hilft Ihnen nicht, wenn eine externe Bibliothek #include ""ihre internen Header in die API einbezieht.

Bart van Ingen Schenau
quelle
1

erreicht das nicht #Pragma once? Sie können etwas so oft einfügen, wie Sie möchten, entweder direkt oder über verkettete Includes. Solange sich #Pragma onceneben jedem ein Include befindet, wird der Header nur einmal eingefügt.

Um dies zu erzwingen, könnten Sie vielleicht ein Build-System erstellen, das nur jeden Header für sich mit einer Dummy-Hauptfunktion enthält, um sicherzustellen, dass er kompiliert wird. #ifdefKette enthält für beste Ergebnisse mit dieser Testmethode.

Ericson2314
quelle
1

Fügen Sie Header-Dateien immer in die CPP-Datei ein. Dies verkürzt nicht nur die Kompilierungszeit erheblich, sondern erspart Ihnen auch viel Ärger, wenn Sie sich für vorkompilierte Header entscheiden. Nach meiner Erfahrung lohnt es sich, auch nur die Mühe zu machen, Erklärungen abzugeben. Regel nur bei Bedarf brechen.

Gvozden
quelle
Können Sie erklären, wie dies "die Kompilierungszeit erheblich verkürzt"?
Mawg sagt, Monica
1
Dadurch wird sichergestellt, dass jede Kompilierungseinheit (CPP-Datei) während der Kompilierungszeit nur das Minimum an Include-Dateien abruft. Andernfalls bilden sich beim Einfügen von Includes in H-Dateien schnell falsche Abhängigkeitsketten, und Sie ziehen bei jeder Kompilierung alle Includes ab.
Gvozden
Mit Include-Wachen könnte ich "signifikant" fragen, aber ich nehme an, dass der Festplattenzugriff (um diese inlcude Gauards zu entdecken) "langsam" ist, also wird der Punkt abgetreten. Vielen Dank für die Klarstellung
Mawg sagt, Monica
0

Ich würde sagen, diese Konvention hat sowohl Vor- als auch Nachteile. Einerseits ist es schön, genau zu wissen, was Ihre CPP-Datei enthält. Andererseits kann die Liste der Includes leicht zu einer lächerlichen Größe werden.

Eine Möglichkeit, diese Konvention zu fördern, besteht darin, nichts in Ihre eigenen Header aufzunehmen, sondern nur in CPP-Dateien. Dann wird jede CPP-Datei, die Ihren Header verwendet, nur kompiliert, wenn Sie explizit alle anderen Header angeben, von denen sie abhängt.

Wahrscheinlich ist hier ein vernünftiger Kompromiss angebracht. Beispielsweise können Sie entscheiden, dass es in Ordnung ist, Standardbibliotheksheader in Ihre eigenen Header aufzunehmen, jedoch nicht mehr.

Dima
quelle
3
Wenn Sie Includes aus Headern herauslassen, spielt die Include-Reihenfolge eine Rolle ... und das möchte ich auf jeden Fall vermeiden.
M. Dudley
1
-1: Erzeugt einen Abhängigkeitsalptraum. Eine Verbesserung von xh könnte eine Änderung in JEDER .cpp-Datei erfordern, die xh
Kevin Cline enthält.
1
@ M.Dudley, wie gesagt, es gibt Vor- und Nachteile.
Dima