C / C ++ obejmują kolejność plików nagłówkowych

288

W jakiej kolejności należy podać pliki, tj. Jakie są powody umieszczania jednego nagłówka przed drugim?

Na przykład, czy pliki systemowe, STL i Boost idą przed czy po lokalnych plikach dołączanych?

Anycorn
źródło
2
Mnóstwo odpowiedzi poniżej wyjaśnia, dlaczego programiści Java zdecydowali się na oddzielne nagłówki. :-) Jednak kilka naprawdę dobrych odpowiedzi, szczególnie napomnienie, aby twoje własne pliki nagłówkowe mogły być samodzielne.
Chris K
37
Podoba mi się to, jak pytania, które mają ponad 100 głosów i są oczywiście interesujące dla dość wielu ludzi, zamykają się jako „mało konstruktywne”.
Andreas,
Gorąco polecam: cplusplus.com/forum/articles/10627
Kalsan
3
@mrt, SO zdecydowanie przypomina społeczności nazistowskiej zupy: albo przestrzegasz bardzo surowych zasad, albo „Nie ma dla ciebie właściwej odpowiedzi / komentarza!”. Niemniej jednak, jeśli ktoś ma problem związany w jakikolwiek sposób do programowania, to jest (zazwyczaj) pierwsza strona iść ..
Imago

Odpowiedzi:

289

Nie sądzę, aby istniało zalecane zamówienie, o ile się kompiluje! Irytujące jest to, że niektóre nagłówki wymagają najpierw dołączenia innych nagłówków ... To jest problem z samymi nagłówkami, a nie z kolejnością dołączania.

Moje osobiste preferencje to przejście z lokalnego na globalny, każdy podsekcja w kolejności alfabetycznej, tj .:

  1. plik h odpowiadający temu plikowi CPP (jeśli dotyczy)
  2. nagłówki z tego samego komponentu,
  3. nagłówki z innych komponentów,
  4. nagłówki systemowe.

Moim uzasadnieniem dla 1. jest to, że powinno to udowodnić, że każdy nagłówek (dla którego jest procesor) może być #included bez wymagań wstępnych (terminus technicus: nagłówek jest „samowystarczalny”). A reszta wydaje się płynąć stamtąd logicznie.

squelart
źródło
16
Prawie tak samo jak ty, z wyjątkiem tego, że przechodzę z globalnego na lokalny, a nagłówek odpowiadający plikowi źródłowemu nie jest traktowany specjalnie.
Jon Purdy
127
@Jon: Powiedziałbym, że jest wręcz przeciwnie! :-) Argumentowałbym, że twoja metoda może wprowadzić ukryte zależności, powiedzmy, że jeśli myclass.cpp zawiera <łańcuch> to <myclass.h>, nie ma sposobu, aby złapać w czasie kompilacji, że myclass.h może zależeć od łańcucha; więc jeśli później ty lub ktoś inny użyje myclass.h, ale nie potrzebuje ciągu, otrzymasz błąd, który należy naprawić w cpp lub w samym nagłówku. Ale chciałbym wiedzieć, czy to, co ludzie myślą, na dłuższą metę będzie działać lepiej ... Dlaczego nie opublikujesz odpowiedzi ze swoją propozycją, a my zobaczymy, kto „wygra”? ;-)
squelart
3
Specyficzne dla ogólnego porządku jest to, czego używam w tym czasie z rekomendacji Dave'a Abrahamsa. I zauważa ten sam powód, dla którego @squelart podświetlenia brakującego nagłówka zawiera w źródłach, od lokalnego do bardziej ogólnego. Ważnym kluczem jest to, że bardziej prawdopodobne jest, że popełnisz te błędy, niż biblioteki zewnętrzne i biblioteki systemowe.
GrafikRobot
7
@PaulJansen To zła praktyka i dobrze jest zastosować technikę, która z dużym prawdopodobieństwem ją wysadzi, aby zła praktyka mogła zostać naprawiona zamiast ukrywania się. FTW od lokalnego do globalnego
bames53
10
@PaulJansen Tak, miałem na myśli unieważnienie standardowego zachowania. Może się to zdarzyć przypadkowo, tak jak na przykład złamanie ODR może nastąpić przypadkowo. Rozwiązaniem nie jest stosowanie praktyk, które ukrywają się, gdy zdarzają się takie wypadki, ale stosowanie praktyk, które najprawdopodobniej spowodują, że wybuchną tak głośno, jak to możliwe, aby błędy można było zauważyć i naprawić jak najwcześniej.
bames53
106

Należy pamiętać, że nagłówki nie powinny być zależne od włączenia innych nagłówków. Jednym ze sposobów zapewnienia tego jest dołączenie nagłówków przed innymi nagłówkami.

Wspomina o tym w szczególności „Thinking in C ++”, nawiązując do „Lakos C ++ Software Design”:

Można uniknąć ukrytych błędów użytkowania, upewniając się, że plik .h komponentu analizuje się sam - bez deklaracji lub definicji dostarczonych zewnętrznie ... Dołączenie pliku .h jako pierwszej linii pliku .c gwarantuje, że żaden kluczowy fragment informacji właściwych dla fizycznego interfejsu komponentu brakuje w pliku .h (lub, jeśli tak, to dowiesz się o tym, jak tylko spróbujesz skompilować plik .c).

To znaczy, uwzględnij w następującej kolejności:

  1. Prototyp / nagłówek interfejsu dla tej implementacji (tzn. Plik .h / .hh, który odpowiada temu plikowi .cpp / .cc).
  2. Inne nagłówki z tego samego projektu, w razie potrzeby.
  3. Nagłówki z innych niestandardowych bibliotek niesystemowych (na przykład Qt, Eigen itp.).
  4. Nagłówki z innych „prawie standardowych” bibliotek (na przykład Boost)
  5. Standardowe nagłówki C ++ (na przykład iostream, funkcjonalne itp.)
  6. Standardowe nagłówki C (na przykład cstdint, dirent.h itp.)

Jeśli któryś z nagłówków ma problem z dołączeniem do tej kolejności, napraw je (jeśli twoje) lub nie używaj ich. Biblioteki bojkotu, które nie piszą czystych nagłówków.

C ++ przewodnik redakcyjny Google twierdzi, prawie na odwrót, ze naprawdę nie ma uzasadnienia w ogóle; Osobiście wolę podejście Lakos.

Nathan Paul Simons
źródło
13
Na razie Przewodnik po stylu Google C ++ zaleca najpierw dołączenie odpowiedniego pliku nagłówka, zgodnie z sugestią Lakos.
Filip Bártek
Nie wychodzisz daleko poza pierwszy powiązany nagłówek, ponieważ kiedy zaczniesz dołączać nagłówki w projekcie, wciągniesz wiele zależności systemowych.
Micheasza
@ Micheasz - nagłówki w projekcie przyciągające „wiele zależności systemowych” są złym projektem, właśnie tego staramy się tutaj unikać. Chodzi o to, aby uniknąć zarówno niepotrzebnych uwzględnień, jak i nierozwiązanych zależności. Wszystkie nagłówki powinny być możliwe do włączenia bez uprzedniego włączenia innych nagłówków. W przypadku, gdy nagłówek w projekcie wymaga zależności systemowej, niech tak będzie - wtedy nie uwzględnisz (i nie powinieneś) zależności zależnej od systemu, chyba że kod lokalny dla tego pliku używa elementów z tego systemu. Nie możesz i nie powinieneś polegać na nagłówkach (nawet twoich), aby uwzględnić używane przez siebie funkcje systemowe.
Nathan Paul Simons
49

Przestrzegam dwóch prostych zasad, które pozwalają uniknąć ogromnej większości problemów:

  1. Wszystkie nagłówki (i wszystkie pliki źródłowe) powinny zawierać to, czego potrzebują. Powinny one nie polegać na swoich użytkowników, w tym rzeczy.
  2. Jako dodatek, wszystkie nagłówki powinny zawierać strażników, aby nie zostały one uwzględnione wiele razy przez zbyt ambitne zastosowanie powyższej zasady 1.

Przestrzegam również wytycznych:

  1. Najpierw dołącz nagłówki systemowe (stdio.h itp.) Z linią podziału.
  2. Pogrupuj je logicznie.

Innymi słowy:

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

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

Chociaż będąc wytycznymi, jest to subiektywna sprawa. Z drugiej strony reguły, egzekwuję sztywno, nawet do tego stopnia, że ​​udostępniam pliki nagłówkowe „wrapper” z włączonymi strażnikami i pogrupowane, jeśli jakiś wstrętny zewnętrzny programista nie podzieli się moją wizją :-)

paxdiablo
źródło
6
+1 „Wszystkie nagłówki (i wszystkie pliki źródłowe) powinny zawierać to, czego potrzebują. Nie powinny polegać na swoich użytkownikach, w tym na różnych rzeczach.” Jednak tak wiele osób polega na tym domyślnym zachowaniu włączenia, na przykład w NULL i nie zawiera <cstddef>. To takie denerwujące, gdy próbuję przenieść ten kod i otrzymuję błędy kompilacji na NULL (jeden z powodów, dla których teraz używam 0).
stinky472
20
Dlaczego najpierw dołączasz nagłówki systemu? Byłoby lepiej z drugiej strony z powodu twojej pierwszej reguły.
jhasse
Jeśli korzystasz z makr funkcji testowych, najprawdopodobniej pierwszą zawartością NIE powinien być standardowy nagłówek biblioteki. Dlatego ogólnie rzecz biorąc, powiedziałbym, że polityka „najpierw lokalnie, później globalnie” jest najlepsza.
Hmijail opłakuje odejście
1
Jeśli chodzi o twoją pierwszą sugestię „nie polegania na użytkownikach”, co powiesz na przesyłanie dalej deklaracji w pliku nagłówkowym, które nie wymagają dołączenia pliku nagłówkowego? Powinniśmy nadal dołączać plik nagłówkowy, ponieważ deklaracje przekazywania nakładają na użytkownika pliku nagłówkowego ciężar dołączenia odpowiednich plików.
Zoso
22

Aby dodać własną cegłę do ściany.

  1. Każdy nagłówek musi być samowystarczalny, który można przetestować tylko wtedy, gdy zostanie dołączony przynajmniej raz
  2. Nie należy omyłkowo modyfikować znaczenia nagłówka innej firmy, wprowadzając symbole (makro, typy itp.)

Więc zwykle tak wyglądam:

// 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"

Każda grupa oddzielona pustą linią od następnej:

  • Najpierw nagłówek odpowiadający temu plikowi cpp (kontrola poprawności)
  • Nagłówki systemowe
  • Nagłówki innych firm, uporządkowane według kolejności zależności
  • Nagłówki projektu
  • Nagłówki prywatne projektu

Zauważ też, że oprócz nagłówków systemowych każdy plik znajduje się w folderze o nazwie jego przestrzeni nazw, tylko dlatego, że łatwiej jest go wyśledzić w ten sposób.

Matthieu M.
źródło
2
Aby inne pliki nagłówkowe nie miały na nie wpływu. Zarówno przez to, co definiują te nagłówki systemowe (zarówno X, jak i Windows zawierają złe informacje o #definetym, że psują inny kod) i zapobiegają niejawnym zależnościom. Na przykład, jeśli nasz podstawowy plik nagłówkowy kodu foo.hnaprawdę zależy, <map>ale wszędzie, gdzie był używany w .ccplikach, <map>zdarzyło się, że już został dołączony, prawdopodobnie nie zauważymy. Aż ktoś próbował dołączyć foo.hbez uprzedniego włączenia <map>. A potem byliby zirytowani.
@ 0A0D: Drugi problem nie jest tutaj problemem w kolejności, ponieważ każdy .hma co najmniej jeden, .cppktóry go obejmuje jako pierwszy (w moim osobistym kodzie powiązany test jednostkowy obejmuje go pierwszy, a kod źródłowy obejmuje go w odpowiedniej grupie ). Jeśli chodzi o brak wpływu, jeśli którykolwiek z nagłówków zawiera, <map>to i tak wszystkie nagłówki później zostaną zmienione , więc wydaje mi się, że przegrywam bitwę.
Matthieu M.,
1
Oczywiście, dlatego regularnie chodzę dookoła i poprawiam starszy kod (lub nawet nowszy kod), który wymaga niepotrzebnego dołączenia, ponieważ tylko skraca czas kompilacji.
@MatthieuM. Chciałbym poznać uzasadnienie twojego punktu pierwszego, tj Header corresponding to this cpp file first (sanity check). Czy jest coś szczególnego, jeśli #include "myproject/example.h"zostanie przeniesiony na koniec wszystkich dołączeń?
MNS,
1
@MNS: Nagłówek powinien być wolnostojący, tzn. Nie powinien wymagać dołączania żadnego innego nagłówka przed nim. Twoim obowiązkiem, jako twórcą nagłówka, jest upewnienie się o tym, a najlepszym sposobem na to jest posiadanie jednego pliku źródłowego, w którym ten nagłówek jest zawarty jako pierwszy. Korzystanie z pliku źródłowego odpowiadającego plikowi nagłówkowemu jest łatwe, innym dobrym wyborem jest użycie pliku źródłowego testu jednostkowego odpowiadającego plikowi nagłówkowemu, ale jest on mniej uniwersalny (mogą nie być testów jednostkowych).
Matthieu M.,
16

Polecam:

  1. Nagłówek budowanego modułu .cc. (Pomaga upewnić się, że każdy nagłówek w projekcie nie ma niejawnych zależności od innych nagłówków w projekcie).
  2. Pliki systemowe C.
  3. Pliki systemowe C ++.
  4. Platforma / system operacyjny / inne pliki nagłówkowe (np. Win32, gtk, openGL).
  5. Inne pliki nagłówkowe z twojego projektu.

I oczywiście, w miarę możliwości, kolejność alfabetyczna w każdej sekcji.

Zawsze używaj deklaracji przesyłania dalej, aby uniknąć niepotrzebnych #includeznaków w plikach nagłówka.

i_am_jorf
źródło
+1, ale dlaczego alfabetycznie? Wydaje się, że możesz poczuć się lepiej, ale nie ma praktycznych korzyści.
Ben
9
Alfabetyczny jest uporządkowaniem arbitralnym, ale łatwym. Nie musisz robić alfabetu, ale musisz wybrać porządek, aby wszyscy robili to konsekwentnie. Odkryłem, że pomaga to uniknąć duplikatów i ułatwia scalanie. A jeśli użyjesz wysublimowanego tekstu, F5 zamówi je dla ciebie.
i_am_jorf
14

Jestem prawie pewien, że nie jest to zalecana praktyka nigdzie w zdrowym świecie, ale lubię, aby system zawierał według długości nazwy pliku, posortowanej leksykalnie w tej samej długości. Tak jak:

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

Myślę, że dobrym pomysłem jest dołączanie własnych nagłówków przed innymi ludźmi, aby uniknąć wstydu związania z kolejnością dołączania.

clstrfsck
źródło
3
Lubię sortować nagłówki, używając klucza składającego się z drugiej, trzeciej, a następnie pierwszej litery w tej kolejności :-) Więc wektor, zestaw, algorytm, funkcjonalne dla twojego przykładu.
paxdiablo
@paxdiablo, dzięki za wskazówkę. Zastanawiam się, czy go użyć, ale obawiam się, że może to spowodować niestabilność stosu nazw plików i ryzyko przewrócenia. Kto wie, co może być uwzględnione, jeśli tak się stanie - może nawet windows.h.
clstrfsck
40
Posortowane według długości ? Niepoczytalność!
James McNellis
1
+1 dla pierwszego. To ma sens, jeśli potrzebujesz wizualnie zlokalizować nagłówki w pliku za pomocą oczu, jest to o wiele lepsze niż alfabetyczne.
Kugel
6

To nie jest subiektywne. Upewnij się, że nagłówki nie polegają na #includebw określonej kolejności. Możesz być pewien, że nie ma znaczenia, w jakiej kolejności dołączasz nagłówki STL lub Boost.

wilhelmtell
źródło
1
Nie zakładałem żadnych ukrytych zależności
Anycorn
Tak, ale kompilator nie może przyjąć tego założenia, więc #include <A>, <B> nigdy nie jest tym samym co #include <B>, <A>, dopóki nie zostaną skompilowane.
Michaił
4

Najpierw dołącz nagłówek odpowiadający .cpp ... innymi słowy, source1.cpppowinien zawierać source1.hprzed dołączeniem czegokolwiek innego. Jedyny wyjątek, jaki mogę wymyślić, to użycie MSVC ze wstępnie skompilowanymi nagłówkami, w którym to przypadku musisz dołączyćstdafx.h wszystko inne.

Uzasadnienie: Uwzględnienie source1.hwcześniejszych plików gwarantuje, że może on działać samodzielnie bez swoich zależności. Jeśli source1.hpóźniej przyjmie zależność, kompilator natychmiast powiadomi Cię o dodaniu wymaganych deklaracji przesyłania dalej source1.h. To z kolei zapewnia, że ​​nagłówki mogą być uwzględniane w dowolnej kolejności przez osoby pozostające na ich utrzymaniu.

Przykład:

źródło1.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
...

Użytkownicy MSVC: Zdecydowanie polecam korzystanie ze wstępnie skompilowanych nagłówków. Więc przenieś wszystkie #includedyrektywy dla standardowych nagłówków (i innych nagłówków, które nigdy się nie zmienią) na stdafx.h.

Agnel Kurian
źródło
2

Uwzględnij od najbardziej specyficznego do najmniej określonego, zaczynając od odpowiedniego pliku .hpp dla pliku .cpp, jeśli taki istnieje. W ten sposób zostaną ujawnione wszelkie ukryte zależności w plikach nagłówkowych, które nie są samowystarczalne.

Komplikuje to użycie wstępnie skompilowanych nagłówków. Jednym ze sposobów obejścia tego jest to, że nie czyniąc specyficznego dla kompilatora projektu, można użyć jednego z nagłówków projektu jako wstępnie skompilowanego pliku dołączanego nagłówka.

dcw
źródło
1

To trudne pytanie w świecie C / C ++ z tak wieloma elementami poza standardem.

Myślę, że kolejność plików nagłówkowych nie stanowi poważnego problemu, o ile się kompiluje, jak powiedział Squelart.

Moje pomysły są następujące: jeśli nie ma konfliktu symboli we wszystkich tych nagłówkach, każda kolejność jest OK, a problem zależności nagłówka można rozwiązać później, dodając #include wiersze do wadliwego .h.

Prawdziwy kłopot powstaje, gdy jakiś nagłówek zmienia swoje działanie (sprawdzając warunki # if) zgodnie z nagłówkami powyżej.

Na przykład w pliku stddef.h w VS2005 znajduje się:

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

Teraz problem: jeśli mam niestandardowy nagłówek („custom.h”), który musi być używany z wieloma kompilatorami, w tym starszymi, które nie zawierają offsetofnagłówków systemowych, powinienem napisać w moim nagłówku:

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

I pamiętaj, aby poinformować użytkownika #include "custom.h" po wszystkich nagłówkach systemowych, w przeciwnym razie wiersz offsetofw pliku stddef.h spowoduje błąd redefinicji makra.

Modlimy się, aby nie spotkać się z takimi przypadkami w naszej karierze.

Jimm Chen
źródło