C / C ++ enthält die Reihenfolge der Header-Dateien

287

In welcher Reihenfolge sollten Dateien angegeben werden, dh aus welchen Gründen wird ein Header vor einem anderen eingefügt?

Gehen die Systemdateien STL und Boost beispielsweise vor oder nach den lokalen Include-Dateien?

Anycorn
quelle
2
Die Vielzahl der Antworten unten ist der Grund, warum sich die Java-Entwickler gegen separate Header entschieden haben. :-) Einige wirklich gute Antworten, insbesondere die Ermahnung, sicherzustellen, dass Ihre eigenen Header-Dateien für sich allein stehen können.
Chris K
37
Ich mag es, wie Fragen, die mehr als 100 Stimmen haben und für einige Leute offensichtlich interessant sind, als "nicht konstruktiv" geschlossen werden.
Andreas
Eine sehr empfehlenswerte Lektüre: cplusplus.com/forum/articles/10627
Kalsan
3
@mrt, SO erinnert stark an die Suppen-Nazi-Community: Entweder befolgst du einige sehr strenge Regeln oder "Keine richtige Antwort / Kommentare für dich!". Wenn jedoch jemand ein Problem mit der Programmierung hat, ist dies (normalerweise) die erste Seite, die besucht wird.
Imago

Antworten:

289

Ich glaube nicht, dass es eine empfohlene Bestellung gibt, solange sie kompiliert wird! Was ärgerlich ist, ist, wenn einige Header erfordern, dass andere Header zuerst eingeschlossen werden ... Das ist ein Problem mit den Headern selbst, nicht mit der Reihenfolge der Includes.

Meine persönliche Präferenz ist es, von lokal zu global zu wechseln, wobei jeder Unterabschnitt in alphabetischer Reihenfolge angezeigt wird, dh:

  1. h-Datei, die dieser CPP-Datei entspricht (falls zutreffend)
  2. Header aus derselben Komponente,
  3. Header von anderen Komponenten,
  4. Systemheader.

Meine Begründung für 1. ist, dass es beweisen sollte, dass jeder Header (für den es einen CPP gibt) #includeohne Voraussetzungen d sein kann (terminus technicus: Header ist "in sich geschlossen"). Und der Rest scheint nur logisch von dort zu fließen.

Squelart
quelle
16
Ziemlich genau wie Sie, außer dass ich von global nach lokal wechsle und der Header, der der Quelldatei entspricht, keine besondere Behandlung erhält.
Jon Purdy
127
@ Jon: Ich würde sagen, das ist so ziemlich das Gegenteil! :-) Ich würde argumentieren, dass Ihre Methode versteckte Abhängigkeiten einführen kann. Wenn myclass.cpp beispielsweise <string> und dann <myclass.h> enthält, gibt es zum Zeitpunkt der Erstellung keine Möglichkeit zu erkennen, dass myclass.h selbst von string abhängt. Wenn Sie oder eine andere Person später myclass.h einschließen, aber keine Zeichenfolge benötigen, wird ein Fehler angezeigt, der entweder im cpp oder im Header selbst behoben werden muss. Aber es würde mich interessieren, ob die Leute denken, dass dies auf lange Sicht besser funktionieren würde ... Warum postest du keine Antwort mit deinem Vorschlag und wir werden sehen, wer "gewinnt"? ;-)
Squelart
3
Die spezifische allgemeine Reihenfolge verwende ich derzeit auf Empfehlung von Dave Abrahams. Und er bemerkt den gleichen Grund wie @squelart, fehlende Header in Quellen zu beleuchten, von lokal bis allgemeiner. Der wichtige Schlüssel ist, dass Sie diese Fehler mit größerer Wahrscheinlichkeit machen als Drittanbieter und Systembibliotheken.
GrafikRobot
7
@PaulJansen Das ist eine schlechte Praxis und es ist gut, eine Technik zu verwenden, die mit größerer Wahrscheinlichkeit in die Luft sprengt, damit die schlechte Praxis behoben werden kann, anstatt sie zu verbergen. lokal zu global FTW
bames53
10
@PaulJansen Ja, ich bezog mich auf das Überschreiben des Standardverhaltens. Es könnte zufällig passieren, genauso wie zum Beispiel das Brechen des ODR versehentlich passieren kann. Die Lösung besteht nicht darin, Praktiken zu verwenden, die sich verbergen, wenn solche Unfälle passieren, sondern Praktiken, die am wahrscheinlichsten dazu führen, dass sie so laut wie möglich in die Luft jagen, damit die Fehler so früh wie möglich erkannt und behoben werden können.
Bames53
106

Das Wichtigste ist, dass Ihre Header nicht davon abhängig sein sollten, dass zuerst andere Header eingefügt werden. Eine Möglichkeit, dies sicherzustellen, besteht darin, Ihre Header vor allen anderen Headern einzuschließen.

Insbesondere "Thinking in C ++" erwähnt dies und verweist auf Lakos '"Large Scale C ++ Software Design":

Latente Verwendungsfehler können vermieden werden, indem sichergestellt wird, dass die .h-Datei einer Komponente von selbst analysiert wird - ohne extern bereitgestellte Deklarationen oder Definitionen ... Wenn Sie die .h-Datei als allererste Zeile der .c-Datei einschließen, wird sichergestellt, dass kein kritisches Element vorhanden ist In der .h-Datei fehlen Informationen, die der physischen Schnittstelle der Komponente eigen sind (oder, falls vorhanden, Sie werden davon erfahren, sobald Sie versuchen, die .c-Datei zu kompilieren).

Das heißt, in der folgenden Reihenfolge einschließen:

  1. Der Prototyp / Interface-Header für diese Implementierung (dh die .h / .hh-Datei, die dieser .cpp / .cc-Datei entspricht).
  2. Andere Header aus demselben Projekt nach Bedarf.
  3. Header aus anderen nicht standardmäßigen Nicht-Systembibliotheken (z. B. Qt, Eigen usw.).
  4. Header aus anderen "fast Standard" -Bibliotheken (z. B. Boost)
  5. Standard-C ++ - Header (z. B. iostream, funktional usw.)
  6. Standard-C-Header (z. B. cstdint, dirent.h usw.)

Wenn einer der Header ein Problem mit der Aufnahme in diese Reihenfolge hat, beheben Sie ihn entweder (falls vorhanden) oder verwenden Sie ihn nicht. Boykottieren Sie Bibliotheken, die keine sauberen Header schreiben.

Googles C ++ - Styleguide argumentiert fast umgekehrt, ohne wirklich zu rechtfertigen. Ich persönlich tendiere dazu, den Lakos-Ansatz zu bevorzugen.

Nathan Paul Simons
quelle
13
Ab sofort empfiehlt Google C ++ Style Guide , zuerst die zugehörige Header-Datei gemäß dem Vorschlag von Lakos einzuschließen.
Filip Bártek
Sie kommen nicht viel über den ersten zugehörigen Header hinaus, da Sie, sobald Sie Ihre projektinternen Header einbinden, viele Systemabhängigkeiten einbeziehen werden.
Micah
@Micah - In-Project-Header, die "viele Systemabhängigkeiten" verursachen, sind schlechtes Design, genau das, was wir hier vermeiden wollen. Es geht darum, sowohl unnötige Includes als auch ungelöste Abhängigkeiten zu vermeiden. Alle Header sollten eingeschlossen werden können, ohne zuerst andere Header einzuschließen. In dem Fall, in dem ein projektinterner Header eine Systemabhängigkeit benötigt, sollten Sie die Systemabhängigkeit danach nicht einschließen (und sollten dies auch nicht tun), es sei denn, der in dieser Datei lokale Code verwendet Dinge aus dieser Systemabhängigkeit. Sie können und sollten sich nicht darauf verlassen, dass Header (auch Ihre) System-Deps enthalten, die Sie verwenden.
Nathan Paul Simons
49

Ich folge zwei einfachen Regeln, die die überwiegende Mehrheit der Probleme vermeiden:

  1. Alle Header (und in der Tat alle Quelldateien) sollten enthalten, was sie benötigen. Sie sollten sich nicht auf ihre Benutzer verlassen, einschließlich der Dinge.
  2. Als Ergänzung sollten alle Header Schutzvorrichtungen enthalten, damit sie durch die zu ehrgeizige Anwendung der obigen Regel 1 nicht mehrmals eingeschlossen werden.

Ich folge auch den Richtlinien von:

  1. Fügen Sie zuerst Systemheader (stdio.h usw.) mit einer Trennlinie ein.
  2. Gruppieren Sie sie logisch.

Mit anderen Worten:

#include <stdio.h>
#include <string.h>

#include "btree.h"
#include "collect_hash.h"
#include "collect_arraylist.h"
#include "globals.h"

Als Richtlinien ist das allerdings eine subjektive Sache. Auf der anderen Seite setze ich die Regeln strikt durch, bis ich Wrapper-Header-Dateien mit Include-Guards und gruppierten Includes versorge, wenn ein widerlicher Entwickler von Drittanbietern meine Vision nicht abonniert :-)

paxdiablo
quelle
6
+1 "Alle Header (und in der Tat alle Quelldateien) sollten das enthalten, was sie benötigen. Sie sollten sich nicht darauf verlassen, dass ihre Benutzer Dinge enthalten." Dennoch verlassen sich so viele Menschen auf dieses implizite Einschlussverhalten, beispielsweise mit NULL, und schließen <cstddef> nicht ein. Es ist so ärgerlich, wenn man versucht, diesen Code zu portieren und Kompilierungsfehler auf NULL zu bekommen (ein Grund, warum ich jetzt nur 0 verwende).
stinky472
20
Warum fügen Sie zuerst Systemheader ein? Es wäre besser das andere warum wegen deiner ersten Regel herum.
jhasse
Wenn Sie Funktionstestmakros verwenden, sollte Ihr erstes Include höchstwahrscheinlich KEIN Standardbibliotheksheader sein. Aus allgemeinen Gründen würde ich daher sagen, dass die Richtlinie "lokal zuerst, global später" am besten ist.
Hmijail trauert um Rücktritte
1
Was ist mit Ihrem ersten Vorschlag, sich nicht auf Benutzer zu verlassen, mit Weiterleitungsdeklarationen in der Header-Datei, für die die Header-Datei nicht enthalten sein muss? Sollten wir die Header-Datei weiterhin einschließen, weil Vorwärtsdeklarationen den Benutzer der Header-Datei dazu verpflichten, die entsprechenden Dateien einzuschließen.
Zoso
22

Um meinen eigenen Ziegelstein an die Wand zu hängen.

  1. Jeder Header muss autark sein, was nur getestet werden kann, wenn er mindestens einmal zuerst enthalten ist
  2. Man sollte die Bedeutung eines Headers eines Drittanbieters nicht fälschlicherweise durch Einfügen von Symbolen (Makro, Typen usw.) ändern.

Also gehe ich normalerweise so:

// myproject/src/example.cpp
#include "myproject/example.h"

#include <algorithm>
#include <set>
#include <vector>

#include <3rdparty/foo.h>
#include <3rdparty/bar.h>

#include "myproject/another.h"
#include "myproject/specific/bla.h"

#include "detail/impl.h"

Jede Gruppe durch eine Leerzeile von der nächsten getrennt:

  • Header, der zuerst dieser CPP-Datei entspricht (Sanity Check)
  • Systemheader
  • Header von Drittanbietern, sortiert nach Abhängigkeitsreihenfolge
  • Projektheader
  • Projekt private Header

Beachten Sie außerdem, dass sich jede Datei, abgesehen von den Systemheadern, in einem Ordner mit dem Namen ihres Namespace befindet, nur weil es einfacher ist, sie auf diese Weise aufzuspüren.

Matthieu M.
quelle
2
Damit andere Header-Dateien nicht davon betroffen sind. Sowohl durch die Definition dieser Systemheader (sowohl X-Includes als auch Windows-Includes sind schlecht #define, wenn es darum geht , anderen Code durcheinander zu bringen) als auch um implizite Abhängigkeiten zu vermeiden. Wenn zum Beispiel unsere Codebasis-Header-Datei foo.hwirklich davon abhängt, <map>aber überall dort, wo sie in .ccDateien verwendet wurde, <map>zufällig bereits enthalten war, würden wir es wahrscheinlich nicht bemerken. Bis jemand versuchte einzuschließen, foo.hohne vorher einzuschließen <map>. Und dann würden sie sich ärgern.
@ 0A0D: Das zweite Problem ist hier kein Problem in der Reihenfolge, da jedes .hmindestens eines enthält .cpp, das es zuerst enthält (in meinem persönlichen Code enthält der zugehörige Komponententest es zuerst und der Quellcode enthält es in seiner rechtmäßigen Gruppe ). Wenn einer der Header nicht beeinflusst wird, werden <map>alle nachfolgend enthaltenen Header ohnehin beeinflusst, so dass es für mich ein verlorener Kampf ist.
Matthieu M.
1
Aus diesem Grund gehe ich regelmäßig herum und korrigiere älteren Code (oder sogar neueren Code), was ein unnötiges Include erfordert, da dies nur die Erstellungszeiten verlängert.
@MatthieuM. Ich würde gerne die Gründe für Ihren ersten Punkt kennen, d Header corresponding to this cpp file first (sanity check). H. Gibt es etwas Besonderes, wenn #include "myproject/example.h"es an das Ende aller Includes verschoben wird?
MNS
1
@MNS: Ein Header sollte freistehend sein, dh es sollte nicht erforderlich sein, einen anderen Header davor einzuschließen. Es liegt in Ihrer Verantwortung als Verfasser des Headers, dies sicherzustellen. Der beste Weg, dies zu tun, besteht darin, eine Quelldatei zu haben, in der dieser Header zuerst enthalten ist. Die Verwendung der der Header-Datei entsprechenden Quelldatei ist einfach. Eine weitere gute Wahl ist die Verwendung der der Header-Datei entsprechenden Unit-Test-Quelldatei, die jedoch weniger universell ist (möglicherweise gibt es keine Unit-Tests).
Matthieu M.
16

Ich empfehle:

  1. Der Header für das .cc-Modul, das Sie erstellen. (Stellt sicher, dass jeder Header in Ihrem Projekt keine impliziten Abhängigkeiten von anderen Headern in Ihrem Projekt aufweist.)
  2. C-Systemdateien.
  3. C ++ - Systemdateien.
  4. Plattform- / Betriebssystem- / andere Header-Dateien (z. B. win32, gtk, openGL).
  5. Andere Header-Dateien aus Ihrem Projekt.

Und natürlich, wenn möglich, in alphabetischer Reihenfolge in jedem Abschnitt.

Verwenden Sie immer Forward-Deklarationen, um unnötige #includes in Ihren Header-Dateien zu vermeiden .

i_am_jorf
quelle
+1, aber warum alphabetisch? Scheint etwas zu sein, durch das Sie sich vielleicht besser fühlen, das aber keinen praktischen Nutzen hat.
Ben
9
Alphabetisch ist eine willkürliche Reihenfolge, aber eine einfache. Sie müssen nicht alphabetisch arbeiten, sondern müssen eine Reihenfolge auswählen, damit jeder dies konsequent tut. Ich habe festgestellt, dass es hilft, Duplikate zu vermeiden und das Zusammenführen zu vereinfachen. Und wenn Sie erhabenen Text verwenden, wird F5 diese für Sie bestellen.
i_am_jorf
14

Ich bin mir ziemlich sicher, dass dies nirgendwo in der vernünftigen Welt eine empfohlene Vorgehensweise ist, aber ich mag es, System-Includes nach Dateinamenlänge zu ordnen, die lexikalisch innerhalb derselben Länge sortiert sind. Wie so:

#include <set>
#include <vector>
#include <algorithm>
#include <functional>

Ich denke, es ist eine gute Idee, eigene Header vor anderen Völkern einzufügen, um die Schande der Abhängigkeit von der Reihenfolge der Einschlüsse zu vermeiden.

clstrfsck
quelle
3
Ich sortiere meine Überschriften gerne mit einem Schlüssel, der aus dem zweiten, dritten und ersten Buchstaben in dieser Reihenfolge besteht :-) Also Vektor, Menge, Algorithmus, funktional für Ihr Beispiel.
Paxdiablo
@paxdiablo, danke für den Tipp. Ich denke darüber nach, es zu verwenden, aber ich befürchte, dass der Dateinamenstapel dadurch instabil wird und wahrscheinlich umkippt. Wer weiß, was in diesem Fall enthalten sein könnte - vielleicht sogar windows.h.
Clstrfsck
40
Nach Länge sortiert ? Wahnsinn!
James McNellis
1
+1 für den ersten. Es ist tatsächlich sinnvoll, wenn Sie Header in einer Datei mit Ihren Augen visuell lokalisieren müssen, ist dies viel besser als alphabetisch.
Kugel
6

Das ist nicht subjektiv. Stellen Sie sicher, dass Ihre Header nicht darauf angewiesen sind, #includein einer bestimmten Reihenfolge d zu sein. Sie können sicher sein, dass es keine Rolle spielt, in welcher Reihenfolge Sie STL- oder Boost-Header einfügen.

wilhelmtell
quelle
1
Ich nahm keine impliziten Abhängigkeiten an
Anycorn
Ja, aber der Compiler kann diese Annahme nicht treffen. Daher ist #include <A>, <B> niemals dasselbe wie #include <B>, <A>, bis sie kompiliert wurden.
Mikhail
4

Fügen Sie zuerst den Header ein, der der .cpp entspricht. Mit anderen Worten, source1.cppsollte enthalten sein, source1.hbevor Sie etwas anderes einfügen . Die einzige Ausnahme, an die ich denken kann, ist die Verwendung von MSVC mit vorkompilierten Headern. In diesem Fall müssen Sie stdafx.hvor allem anderen einschließen .

Begründung: Durch das Einfügen der source1.hvorherigen Dateien wird sichergestellt, dass sie ohne Abhängigkeiten eigenständig sind. Wenn Sie source1.hzu einem späteren Zeitpunkt eine Abhängigkeit annehmen, werden Sie vom Compiler sofort gewarnt, die erforderlichen Weiterleitungsdeklarationen hinzuzufügen source1.h. Dies stellt wiederum sicher, dass Header von ihren Angehörigen in beliebiger Reihenfolge aufgenommen werden können.

Beispiel:

source1.h

class Class1 {
    Class2 c2;    // a dependency which has not been forward declared
};

source1.cpp

#include "source1.h"    // now compiler will alert you saying that Class2 is undefined
                    // so you can forward declare Class2 within source1.h
...

MSVC-Benutzer: Ich empfehle dringend, vorkompilierte Header zu verwenden. Verschieben Sie daher alle #includeAnweisungen für Standardheader (und andere Header, die sich nie ändern werden) nach stdafx.h.

Agnel Kurian
quelle
2

Schließen Sie von der spezifischsten zur am wenigsten spezifischen ein, beginnend mit der entsprechenden .hpp für die .cpp, falls eine solche vorhanden ist. Auf diese Weise werden alle versteckten Abhängigkeiten in Header-Dateien aufgedeckt, die nicht autark sind.

Dies wird durch die Verwendung vorkompilierter Header erschwert. Eine Möglichkeit, dies zu umgehen, besteht darin, ohne den Projekt-Compiler spezifisch zu machen, einen der Projekt-Header als vorkompilierte Header-Include-Datei zu verwenden.

dcw
quelle
1

In der C / C ++ - Welt ist dies eine schwierige Frage, da so viele Elemente über den Standard hinausgehen.

Ich denke, die Reihenfolge der Header-Dateien ist kein ernstes Problem, solange sie kompiliert werden, wie Squelart sagte.

Meine Ideen sind: Wenn in all diesen Headern kein Symbolkonflikt besteht, ist jede Reihenfolge in Ordnung, und das Problem mit der Headerabhängigkeit kann später behoben werden, indem dem fehlerhaften .h Zeilen #include hinzugefügt werden.

Der eigentliche Ärger entsteht, wenn ein Header seine Aktion ändert (indem er die # if-Bedingungen überprüft), je nachdem, welche Header oben stehen.

In stddef.h in VS2005 gibt es beispielsweise Folgendes:

#ifdef  _WIN64
#define offsetof(s,m)   (size_t)( (ptrdiff_t)&(((s *)0)->m) )
#else
#define offsetof(s,m)   (size_t)&(((s *)0)->m)
#endif

Nun das Problem: Wenn ich einen benutzerdefinierten Header ("custom.h") habe, der mit vielen Compilern verwendet werden muss, einschließlich einiger älterer, die nicht offsetofin ihren Systemheadern enthalten sind, sollte ich in meinen Header schreiben:

#ifndef offsetof
#define offsetof(s,m)   (size_t)&(((s *)0)->m)
#endif

Und stellen Sie sicher, dass Sie den Benutzer #include "custom.h" nach allen System-Headern offsetofanweisen , andernfalls wird in der Zeile in stddef.h ein Makro-Neudefinitionsfehler angezeigt.

Wir beten darum, in unserer Karriere keine solchen Fälle mehr zu erleben.

Jimm Chen
quelle