Jest to jeden z tych rodzajów „raczej” niż „powinien” standardów kodowania. Powodem jest to, że musiałbyś napisać parser C ++, aby go wymusić.
Bardzo powszechną zasadą dla plików nagłówkowych jest to, że muszą one stać same. Plik nagłówkowy nie może wymagać, aby niektóre inne pliki nagłówkowe były # zawarte przed dołączeniem danego nagłówka. Jest to wymaganie do przetestowania. Biorąc pod uwagę jakiś losowy nagłówek foo.hh
, następujące powinny się skompilować i uruchomić:
#include "foo.hh"
int main () {
return 0;
}
Ta reguła ma wpływ na użycie innych klas w niektórych nagłówkach. Czasami tych konsekwencji można uniknąć, zgłaszając dalej te inne klasy. Nie jest to możliwe w przypadku wielu standardowych klas bibliotek. Nie ma możliwości przekazania dalej deklaracji wystąpienia szablonu, takiego jak std::string
lub std::vector<SomeType>
. Musisz do #include
tych nagłówków STL w nagłówku, nawet jeśli jedynym zastosowaniem tego typu jest argument funkcji.
Innym problemem są rzeczy, które przypadkowo wciągasz. Przykład: rozważ następujące kwestie:
plik foo.cc:
#include "foo.hh"
#include "bar.hh"
void Foo::Foo () : bar() { /* body elided */ }
void Foo::do_something (int item) {
...
bar.add_item (item);
...
}
Oto bar
element Foo
danych klasy, który jest typu Bar
. Zrobiłeś to dobrze i masz #included bar.hh, chociaż musiałoby to być uwzględnione w nagłówku definiującym klasę Foo
. Nie uwzględniono jednak rzeczy używanych przez Bar::Bar()
i Bar::add_item(int)
. Istnieje wiele przypadków, w których te połączenia mogą powodować dodatkowe odniesienia zewnętrzne.
Jeśli analizujesz foo.o
za pomocą takiego narzędzia, jak nm
, okaże się, że funkcje foo.cc
wywołują wszelkiego rodzaju rzeczy, dla których nie zrobiłeś odpowiedniego #include
. Czy powinieneś zatem dodać #include
dyrektywy dotyczące tych przypadkowych odniesień zewnętrznych foo.cc
? Odpowiedź absolutnie nie jest. Problem polega na tym, że bardzo trudno jest odróżnić funkcje wywoływane przypadkowo od funkcji wywoływanych bezpośrednio.
#include "x.h"
będzie działał bez konieczności uprzedniego#include
. To wystarczy, jeśli nie będziesz nadużywać#define
.Jeśli musisz egzekwować regułę, że określone pliki nagłówkowe muszą stać samodzielnie, możesz skorzystać z narzędzi, które już masz. Utwórz podstawowy plik makefile, który kompiluje każdy plik nagłówka osobno, ale nie generuje pliku obiektowego. Będziesz mógł określić, w którym trybie chcesz skompilować plik nagłówkowy (tryb C lub C ++) i sprawdzić, czy może on działać samodzielnie. Można przyjąć rozsądne założenie, że dane wyjściowe nie zawierają żadnych wyników fałszywie dodatnich, wszystkie wymagane zależności są deklarowane, a dane wyjściowe są dokładne.
Jeśli używasz IDE, nadal możesz to zrobić bez makefile (w zależności od IDE). Po prostu utwórz dodatkowy projekt, dodaj pliki nagłówkowe, które chcesz zweryfikować, i zmień ustawienia, aby skompilować je jako plik C lub C ++. Na przykład w MSVC zmieniłbyś ustawienie „Typ elementu” w „Właściwości konfiguracji-> Ogólne”.
źródło
Nie sądzę, aby takie narzędzie istniało, ale byłbym szczęśliwy, gdyby inna odpowiedź mnie obaliła.
Problem z pisaniem takiego narzędzia polega na tym, że bardzo łatwo zgłasza on fałszywy wynik, dlatego szacuję przewagę netto takiego narzędzia na bliską zeru.
Jedynym sposobem, w jaki takie narzędzie mogłoby działać, jest zresetowanie tabeli symboli tylko do zawartości przetwarzanego pliku nagłówka, ale wtedy pojawia się problem polegający na tym, że nagłówki tworzące zewnętrzny interfejs API biblioteki delegują rzeczywiste deklaracje do wewnętrzne nagłówki.
Na przykład
<string>
w implementacji GCC libc ++ nic nie deklaruje, ale po prostu zawiera kilka wewnętrznych nagłówków, które zawierają rzeczywiste deklaracje. Gdyby narzędzie zresetowało tabelę symboli do tego, co zostało zadeklarowane przez<string>
siebie, byłoby to niczym.Możesz mieć narzędzie do rozróżnienia między
#include ""
i#include <>
, ale to nie pomaga, jeśli biblioteka zewnętrzna wykorzystuje#include ""
swoje wewnętrzne nagłówki do API.źródło
nie
#Pragma once
osiąga tego? Możesz dołączyć coś tyle razy, ile chcesz, albo bezpośrednio, albo poprzez łańcuchowe dołączenia, i tak długo, jak jest#Pragma once
obok każdego z nich, nagłówek jest dołączany tylko raz.Jeśli chodzi o wymuszanie go, być może mógłbyś stworzyć system kompilacji, który po prostu zawiera każdy nagłówek sam z jakąś sztuczną funkcją główną, aby upewnić się, że się kompiluje.
#ifdef
zawiera najlepsze wyniki dla tej metody testowania.źródło
Zawsze dołączaj pliki nagłówkowe do pliku CPP. To nie tylko znacznie skraca czas kompilacji, ale także oszczędza kłopotów, jeśli zdecydujesz się na prekompilowane nagłówki. Z mojego doświadczenia wynika, że nawet robienie kłopotów z deklaracjami do przodu jest warte praktyki. Łam zasadę tylko wtedy, gdy jest to konieczne.
źródło
Powiedziałbym, że ta konwencja ma zarówno zalety, jak i wady. Z jednej strony dobrze jest wiedzieć dokładnie, co zawiera Twój plik .cpp. Z drugiej strony lista dołączeń może łatwo wzrosnąć do absurdalnych rozmiarów.
Jednym ze sposobów zachęcania do tej konwencji jest nie umieszczanie niczego we własnych nagłówkach, ale tylko w plikach .cpp. Wówczas żaden plik .cpp korzystający z nagłówka nie zostanie skompilowany, chyba że wyraźnie podasz wszystkie inne nagłówki, od których zależy.
Prawdopodobnie jest tu jakiś rozsądny kompromis. Na przykład możesz zdecydować, że możesz umieszczać standardowe nagłówki bibliotek we własnych nagłówkach, ale nie więcej.
źródło