Co powinno, a czego nie powinno być w pliku nagłówkowym? [Zamknięte]

71

Jakie rzeczy absolutnie nigdy nie powinny być zawarte w pliku nagłówkowym?

Jeśli na przykład pracuję z udokumentowanym standardowym formatem przemysłowym, który ma wiele stałych, czy dobrą praktyką jest ich definiowanie w pliku nagłówkowym (jeśli piszę parser dla tego formatu)?

Jakie funkcje powinny przejść do pliku nagłówka?
Jakie funkcje nie powinny?

Moshe Magnes
źródło
1
Krótkie i bezbolesne: definicje i deklinacje potrzebne w więcej niż jednym module.
ott--
21
Oznaczenie tego pytania jako „zbyt szerokiego” i zamknięcie jest absolutnie haniebną przesadą umiaru. To pytanie dokładnie pyta, czego szukam - pytanie jest dobrze sformułowane i zadaje bardzo jasne pytanie: jakie są najlepsze praktyki? Jeśli jest to „zbyt szerokie” do projektowania oprogramowania… moglibyśmy po prostu zamknąć całe forum.
Gewure
TL; DR. W przypadku C ++ w czwartym wydaniu „The C ++ Programming Language” napisanym przez Bjarne Stroustrup (jego twórcę) w rozdziale 15.2.2 opisano, co powinien zawierać nagłówek, a czego nie. Wiem, że otagowałeś pytanie na C, ale niektóre porady mają również zastosowanie. Myślę, że to dobre pytanie ...
horro

Odpowiedzi:

57

Co umieścić w nagłówkach:

  • Minimalny zestaw #includedyrektyw potrzebnych do kompilacji nagłówka, gdy nagłówek jest zawarty w pliku źródłowym.
  • Definicje symboli preprocesora dotyczące rzeczy, które muszą być współużytkowane i które można osiągnąć tylko za pomocą preprocesora. Nawet w C symbole preprocesora najlepiej ograniczyć do minimum.
  • Przekazywanie deklaracji struktur niezbędnych do kompilacji definicji struktur, prototypów funkcji i deklaracji zmiennych globalnych w treści nagłówka.
  • Definicje struktur danych i wyliczeń, które są współużytkowane przez wiele plików źródłowych.
  • Deklaracje funkcji i zmiennych, których definicje będą widoczne dla linkera.
  • Definicje funkcji wbudowanych, ale uważaj tutaj.

Co nie należy do nagłówka:

  • Nieodpłatne #includedyrektywy. Te nieodpłatne obejmują powodowanie ponownej kompilacji rzeczy, które nie wymagają ponownej kompilacji i mogą czasami sprawić, że system nie będzie mógł kompilować. Nie rób #includepliku w nagłówku, jeśli sam nagłówek nie potrzebuje tego innego pliku nagłówka.
  • Symbole preprocesora, których intencja może być osiągnięta przez jakikolwiek mechanizm, dowolny mechanizm inny niż preprocesor.
  • Wiele definicji struktur. Podziel je na osobne nagłówki.
  • Wbudowane definicje funkcji, które wymagają dodatkowych #include, które mogą ulec zmianie lub są zbyt duże. Te wbudowane funkcje powinny mieć niewielki lub żaden wentylator, a jeśli mają wentylator, powinien być zlokalizowany na elementy zdefiniowane w nagłówku.

Co stanowi minimalny zestaw #includeinstrukcji?

To okazuje się nietrywialne pytanie. Definicja TL; DR: Plik nagłówka musi zawierać pliki nagłówkowe, które bezpośrednio definiują każdy z typów bezpośrednio użytych lub które bezpośrednio deklarują każdą z funkcji użytych w danym pliku nagłówkowym, ale nie mogą zawierać niczego innego. Wskaźnik lub typ referencyjny C ++ nie kwalifikuje się jako bezpośrednie użycie; preferowane są odniesienia do przodu.

Jest miejsce na nieuzasadnioną #includedyrektywę, która odbywa się w zautomatyzowanym teście. Dla każdego pliku nagłówka w pakiecie oprogramowania automatycznie generuję, a następnie kompiluję:

#include "path/to/random/header_under_test"
int main () { return 0; }

Kompilacja powinna być czysta (tzn. Wolna od ostrzeżeń i błędów). Ostrzeżenia lub błędy dotyczące niekompletnych lub nieznanych typów oznaczają, że w testowanym pliku nagłówkowym brakuje niektórych #includedyrektyw i / lub brakujących deklaracji forward. Uwaga: tylko dlatego, że wynik testu nie oznacza, że ​​zestaw #includedyrektyw jest wystarczający, a co dopiero minimalny.

David Hammen
źródło
Tak więc, jeśli mam bibliotekę definiującą strukturę o nazwie A, a ta biblioteka o nazwie B używa tej struktury, a biblioteka B jest używana przez program C, czy powinienem dołączyć plik nagłówka biblioteki A do głównego nagłówka biblioteki B, czy powinienem Właśnie przekazałem to zadeklarować? biblioteka A jest kompilowana i łączona z biblioteką B podczas jej kompilacji.
MarcusJ
@MarcusJ - Pierwszą rzeczą, którą wymieniłem w sekcji Co nie należy do nagłówka, było bezpłatne #include. Jeśli plik nagłówka B nie zależy od definicji w pliku nagłówka A, nie # włączaj pliku nagłówka A do pliku nagłówka B. Plik nagłówka nie jest miejscem, w którym można określić zależności stron trzecich lub instrukcje budowania. Te idą gdzie indziej, na przykład do pliku Readme najwyższego poziomu.
David Hammen,
1
@MarcusJ - Zaktualizowałem swoją odpowiedź, próbując odpowiedzieć na twoje pytanie. Pamiętaj, że nie ma jednej odpowiedzi na twoje pytanie. Zilustruję kilka skrajności. Przypadek 1: Jedynym miejscem, w którym biblioteka B bezpośrednio korzysta z funkcji biblioteki A, są pliki źródłowe biblioteki B. Przypadek 2: Biblioteka B jest cienkim rozszerzeniem funkcjonalności w bibliotece A, z plikami nagłówkowymi dla biblioteki B bezpośrednio przy użyciu typów i / lub funkcji zdefiniowanych w bibliotece A. W przypadku 1 nie ma powodu, aby udostępniać bibliotekę A w nagłówek (nagłówki) dla biblioteki B. W przypadku 2 ta ekspozycja jest prawie obowiązkowa.
David Hammen
Tak, to przypadek 2, przepraszam, że mój komentarz pominął fakt, że używa on typów zadeklarowanych w bibliotece A w nagłówkach biblioteki B, myślałem, że mogę przekazać dalej, ale nie sądzę, żeby to zadziałało. Dziękuję za aktualizację.
MarcusJ
Czy dodawanie stałych do pliku nagłówkowego jest dużym nie-nie?
mding5692,
15

Oprócz tego, co już zostało powiedziane.

Pliki H powinny zawsze zawierać:

  • Dokumentacja kodu źródłowego !!! Co najmniej, jaki jest cel różnych parametrów i zwracanych wartości funkcji.
  • Nagłówki, #ifndef MYHEADER_H # zdefiniuj MYHEADER_H ... #endif

Pliki H nigdy nie powinny zawierać:

  • Dowolna forma alokacji danych.
  • Definicje funkcji Funkcje wbudowane mogą być rzadkim wyjątkiem w niektórych przypadkach.
  • Wszystko oznaczone static.
  • Typedefs, #defines lub stałe, które nie mają znaczenia dla reszty aplikacji.

(Powiedziałbym również, że nigdy nie ma powodu, aby używać niestałych zmiennych globalnych / zewnętrznych, ale jest to dyskusja na inny post).


źródło
1
Zgadzam się ze wszystkim, z wyjątkiem tego, co podkreśliłeś. Jeśli tworzysz bibliotekę, tak, powinieneś udokumentować lub użytkowników swojej biblioteki. W przypadku projektu wewnętrznego nie powinieneś zaśmiecać nagłówków dokumentacją, jeśli używasz dobrych, zrozumiałych nazw zmiennych i funkcji.
martiert,
5
@martiert Mam również szkołę „niech kod mówi sam za siebie”. Mimo to powinieneś przynajmniej udokumentować swoje funkcje, nawet jeśli nikt oprócz ciebie ich nie użyje. Szczególnie interesujące są: w przypadku, gdy funkcja obsługuje obsługę błędów, jakie kody błędów zwraca i pod jakimi warunkami nie działa? Co stanie się z parametrami (bufory, wskaźniki itp.), Jeśli funkcja zawiedzie? Kolejna ważna rzecz: czy parametry wskaźnika zwracają coś do dzwoniącego, tj. Czy oczekują przydzielonej pamięci? ->
1
Powinno być oczywiste dla osoby dzwoniącej, jaka obsługa błędów jest wykonywana wewnątrz funkcji, a co nie. Jeśli funkcja spodziewa się przydzielonego bufora, najprawdopodobniej pozostawi również kontrolę poza zakresem dzwoniącemu. Jeśli funkcja wymaga wykonania innej funkcji, należy to udokumentować (tzn. Uruchomić link_list_init () przed link_list_add ()). I wreszcie, jeśli funkcja ma „efekt uboczny”, taki jak tworzenie plików, wątków, timerów itp., Należy to zaznaczyć w dokumentacji. ->
1
Może „dokumentacja kodu źródłowego” jest tutaj zbyt obszerna, to naprawdę należy do kodu źródłowego. „Dokumentacja użytkowania” z danymi wejściowymi i wyjściowymi, warunkami wstępnymi i późniejszymi oraz skutkami ubocznymi powinna zdecydowanie tam pójść, nie w epickiej formie, ale w krótkiej formie.
Zabezpiecz
2
Trochę spóźnione, ale +1 za dokumentację. Dlaczego ta klasa istnieje? Kod nie mówi sam za siebie. Do czego służy ta funkcja? RTFC (przeczytaj dokładny plik .cpp) to nieprzyzwoity czteroliterowy akronim. Nigdy nie powinno się wymagać RTFC dla zrozumienia. Prototyp w nagłówku powinien podsumować, w niektórych możliwych do wyodrębnienia komentarzach (np. Doxygen), jakie są argumenty i co robi funkcja. Dlaczego ten element danych istnieje, co zawiera i czy ma wartość w metrach, stopach lub furlongach? To też jest inny temat dla (możliwych do wyodrębnienia) komentarzy.
David Hammen
4

Prawdopodobnie nigdy nie powiedziałbym nigdy, ale instrukcje, które podczas analizowania generują dane i kod, nie powinny znajdować się w pliku .h.

Makra, wbudowane funkcje i szablony mogą wyglądać jak dane lub kod, ale nie generują kodu podczas analizowania, ale zamiast tego, gdy są używane. Te elementy często muszą być używane w więcej niż jednym .c lub .cpp, więc należą do .h.

Moim zdaniem plik nagłówkowy powinien mieć minimalny praktyczny interfejs do odpowiedniego pliku .c lub .cpp. Interfejs może zawierać #define, class, typedef, definicje struktur, prototypy funkcji i mniej preferowane zewnętrzne definicje zmiennych globalnych. Jeśli jednak deklaracja jest używana tylko w jednym pliku źródłowym, prawdopodobnie powinna zostać wykluczona z .h i zamiast tego powinna być zawarta w pliku źródłowym.

Niektórzy mogą się nie zgadzać, ale moje osobiste kryteria dla plików .h są takie, że # zawierają wszystkie inne pliki .h, które muszą być w stanie skompilować. W niektórych przypadkach może to być wiele plików, więc mamy kilka skutecznych metod zmniejszania zależności zewnętrznych, takich jak deklaracje przekazywania do klas, które pozwalają nam używać wskaźników do obiektów klasy bez uwzględnienia dużego drzewa plików dołączanych.

DeveloperDon
źródło
3

Plik nagłówkowy powinien mieć następującą organizację:

  • typy i stałe definicje
  • deklaracje obiektu zewnętrznego
  • deklaracje funkcji zewnętrznych

Pliki nagłówkowe nigdy nie powinny zawierać definicji obiektów, a jedynie definicje typów i deklaracje obiektów.

theD
źródło
Co z definicjami funkcji wbudowanych?
Kos,
Jeśli funkcja inline jest funkcją „pomocniczą”, która jest używana tylko w jednym module C, umieść ją tylko w tym pliku .c. Jeśli funkcja wstawiana musi być widoczna dla dwóch lub więcej modułów, umieść ją w pliku nagłówkowym.
theD
Ponadto, jeśli funkcja musi być widoczna poza granicami biblioteki, nie rób jej wewnątrz, ponieważ zmusza to wszystkich, którzy korzystają z biblioteki, do ponownej kompilacji za każdym razem, gdy modyfikujesz dane.
Donal Fellows
@DonalFellows: To rozwiązanie odręczne. Lepsza zasada: nie wkładaj do nagłówków treści, które podlegają częstym modyfikacjom. Nie ma nic złego w wstawianiu krótkiej małej funkcji w nagłówku, jeśli funkcja nie ma rozwinięcia i ma jasną definicję, która zmieni się tylko w przypadku zmiany podstawowej struktury danych. Jeśli definicja funkcji ulegnie zmianie z powodu zmiany definicji podstawowej struktury, tak, musisz ponownie skompilować wszystko, ale i tak będziesz musiał to zrobić, ponieważ zmieniła się definicja struktury.
David Hammen
0

Instrukcje generujące dane i kod podczas ich analizowania nie powinny znajdować się w .hpliku. Jeśli chodzi o mój punkt widzenia, plik nagłówkowy powinien mieć minimalny praktyczny interfejs do odpowiedniego .club .cpp.

Ajay Prasad
źródło