Co powinno znaleźć się w pliku .h?


95

Podczas dzielenia kodu na wiele plików, co dokładnie powinno znaleźć się w pliku .h, a co w pliku .cpp?



7
Jest to kwestia czystego stylu, ale uważam, że deklaracje C ++ trafiają do .hpppliku, podczas gdy deklaracje C trafiają do .hpliku. Jest to bardzo przydatne podczas mieszania kodu C i C ++ (na przykład starsze moduły w C).
Thomas Matthews

@ThomasMatthews Makes sense. Czy taka praktyka jest często stosowana?
Ty

@lightningleaf: Tak, ta praktyka jest często używana, zwłaszcza podczas mieszania języków C ++ i C.
Thomas Matthews

Odpowiedzi:


116

Pliki nagłówkowe ( .h) mają na celu dostarczenie informacji, które będą potrzebne w wielu plikach. Rzeczy takie jak deklaracje klas, prototypy funkcji i wyliczenia zwykle znajdują się w plikach nagłówkowych. Jednym słowem „definicje”.

Pliki kodu ( .cpp) są przeznaczone do dostarczania informacji o implementacji, które muszą być znane tylko w jednym pliku. Ogólnie rzecz biorąc, treści funkcji i zmienne wewnętrzne, do których inne moduły nie powinny / nigdy nie będą miały dostępu, należą do .cppplików. Jednym słowem „realizacje”.

Najprostszym pytaniem, które należy sobie zadać, aby określić, co należy do miejsca, jest „czy jeśli to zmienię, będę musiał zmienić kod w innych plikach, aby ponownie się skompilować?” Jeśli odpowiedź brzmi „tak”, prawdopodobnie należy ona do pliku nagłówkowego; jeśli odpowiedź brzmi „nie”, prawdopodobnie należy ona do pliku kodu.


4
Poza tym, że dane z prywatnych zajęć muszą trafiać do nagłówka. Szablony muszą być całkowicie zdefiniowane w nagłówku (chyba że używasz jednego z kilku obsługiwanych kompilatorów export). Jedynym sposobem obejścia nr 1 jest PIMPL. # 2 byłby możliwy, gdyby exportbył obsługiwany i może być możliwy przy użyciu języka c ++ 0x i externszablonów. IMO, pliki nagłówkowe w języku c ++ tracą wiele ze swojej użyteczności.
KitsuneYMG

24
Wszystko dobrze, ale z niedokładną terminologią. Jednym słowem „deklaracje” - termin „definicja” jest synonimem „realizacji”. W nagłówku powinny znajdować się tylko kod deklaratywny, kod wbudowany, definicje makr i kod szablonu; tj. nic, co tworzy instancję kodu lub danych.
Clifford

8
Muszę się zgodzić z Cliffordem. Używasz terminów deklaracji i definicji raczej luźno i w pewien sposób wymiennie. Ale mają precyzyjne znaczenie w C ++. Przykłady: Deklaracja klasy wprowadza nazwę klasy, ale nie podaje jej zawartości. Definicja klasy zawiera listę wszystkich członków i funkcji znajomych. Oba można bez problemu umieścić w plikach nagłówkowych. To, co nazywasz „prototypem funkcji”, jest deklaracją funkcji . Ale definicja funkcji to ta rzecz, która zawiera kod funkcji i powinna zostać umieszczona w pliku CPP - chyba że jest to element wbudowany lub (część) szablonu.
sellibitze

5
Mają precyzyjne znaczenie w C ++, nie mają precyzyjnego znaczenia w języku angielskim. Moja odpowiedź została zapisana w tym drugim.
Amber

56

Faktem jest, że w C ++ jest to nieco bardziej skomplikowane niż organizacja nagłówka / źródła C.

Co widzi kompilator?

Kompilator widzi jeden duży plik źródłowy (.cpp) z poprawnie dołączonymi nagłówkami. Plik źródłowy to jednostka kompilacji, która zostanie wkompilowana w plik obiektowy.

Dlaczego więc potrzebne są nagłówki?

Ponieważ jedna jednostka kompilacji może potrzebować informacji o implementacji w innej jednostce kompilacji. Można więc napisać na przykład implementację funkcji w jednym źródle, a deklarację tej funkcji w innym źródle, aby jej użyć.

W takim przypadku są dwie kopie tych samych informacji. Co jest złe ...

Rozwiązaniem jest udostępnienie kilku szczegółów. Podczas gdy implementacja powinna pozostać w źródle, deklaracja współdzielonych symboli, takich jak funkcje lub definicja struktur, klas, wyliczeń itp., Może wymagać udostępnienia.

Nagłówki służą do umieszczania tych udostępnionych szczegółów.

Przenieś do nagłówka deklaracje tego, co należy udostępniać między wieloma źródłami

Nic więcej?

W C ++ jest kilka innych rzeczy, które można umieścić w nagłówku, ponieważ one również muszą być udostępnione:

  • kod wbudowany
  • szablony
  • stałe (zwykle te, których chcesz używać wewnątrz przełączników ...)

Przejdź do nagłówka WSZYSTKO, co ma być udostępnione, w tym udostępnione implementacje

Czy to oznacza, że ​​w nagłówkach mogą znajdować się źródła?

Tak. W rzeczywistości istnieje wiele różnych rzeczy, które mogą znajdować się w „nagłówku” (tj. Współdzielone między źródłami).

  • Deklaracje forward
  • deklaracje / definicje funkcji / struktur / klas / szablonów
  • implementacja kodu wbudowanego i szablonu

Staje się to skomplikowane, aw niektórych przypadkach (zależności cykliczne między symbolami) niemożliwe jest przechowywanie go w jednym nagłówku.

Nagłówki można podzielić na trzy części

Oznacza to, że w skrajnym przypadku możesz mieć:

  • nagłówek deklaracji do przodu
  • nagłówek deklaracji / definicji
  • nagłówek implementacji
  • źródło implementacji

Wyobraźmy sobie, że mamy szablon MyObject. Moglibyśmy mieć:

// - - - - MyObject_forward.hpp - - - - 
// This header is included by the code which need to know MyObject
// does exist, but nothing more.
template<typename T>
class MyObject ;

.

// - - - - MyObject_declaration.hpp - - - - 
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>

template<typename T>
class MyObject
{
   public :
      MyObject() ;
   // Etc.
} ;

void doSomething() ;

.

// - - - - MyObject_implementation.hpp - - - - 
// This header is included by the code which need to see
// the implementation of the methods/functions of MyObject,
// but nothing more.
#include <MyObject_declaration.hpp>

template<typename T>
MyObject<T>::MyObject()
{
   doSomething() ;
}

// etc.

.

// - - - - MyObject_source.cpp - - - - 
// This source will have implementation that does not need to
// be shared, which, for templated code, usually means nothing...
#include <MyObject_implementation.hpp>

void doSomething()
{
   // etc.
} ;

// etc.

Łał!

W „prawdziwym życiu” jest to zwykle mniej skomplikowane. Większość kodu ma tylko prostą organizację nagłówka / źródła, z pewnym kodem wbudowanym w źródle.

Ale w innych przypadkach (obiekty oparte na szablonach znające się nawzajem) musiałem mieć dla każdego obiektu oddzielne nagłówki deklaracji i implementacji, z pustym źródłem zawierającym te nagłówki, aby pomóc mi zobaczyć niektóre błędy kompilacji.

Innym powodem podzielenia nagłówków na oddzielne nagłówki może być przyspieszenie kompilacji, ograniczenie liczby analizowanych symboli do ściśle niezbędnego poziomu i uniknięcie niepotrzebnej rekompilacji źródła, które dba tylko o deklarację do przodu, gdy zmieniła się implementacja metody wbudowanej.

Wniosek

Powinieneś uczynić swoją organizację kodu zarówno jak najprostszą, jak to tylko możliwe, i jak najbardziej modułową. Umieść jak najwięcej w pliku źródłowym. Ujawniaj w nagłówkach tylko to, co należy udostępnić.

Ale w dniu, w którym będziesz mieć cykliczne zależności między obiektami szablonowymi, nie zdziw się, jeśli organizacja Twojego kodu stanie się nieco bardziej „interesująca” niż zwykła organizacja nagłówka / źródła ...

^ _ ^


18

oprócz wszystkich innych odpowiedzi powiem ci, czego NIE umieszczasz w pliku nagłówkowym:
usingdeklaracja (najczęściej spotykana using namespace std;) nie powinna pojawiać się w pliku nagłówkowym, ponieważ zanieczyszczają przestrzeń nazw pliku źródłowego, w którym jest zawarta .


+1 z zastrzeżeniem, którego możesz użyć, o ile znajduje się w jakiejś szczegółowej przestrzeni nazw (lub anonimowej przestrzeni nazw). Ale tak, nigdy nie używaj usingdo umieszczania rzeczy w globalnej przestrzeni nazw w nagłówku.
KitsuneYMG

+1 Na to znacznie łatwiej odpowiedzieć. :) Ponadto pliki nagłówkowe nie powinny zawierać anonimowych przestrzeni nazw.
sellibitze

Pliki nagłówkowe mogą zawierać anonimowe przestrzenie nazw, o ile rozumiesz, co to oznacza, tj. Że każda jednostka tłumacząca będzie miała inną kopię tego, co definiujesz. Funkcje wbudowane w anonimowych przestrzeniach nazw są zalecane w C ++ w przypadkach, w których static inlineużywałbyś w C99, ponieważ ma to coś wspólnego z tym, co dzieje się, gdy łączysz wewnętrzne powiązania z szablonami. Przestrzenie nazw Anon umożliwiają „ukrywanie” funkcji przy zachowaniu powiązań zewnętrznych.
Steve Jessop

Steve, to, co napisałeś, mnie nie przekonało. Wybierz konkretny przykład, w którym uważasz, że przestrzeń nazw anon ma całkowity sens w pliku nagłówkowym.
sellibitze

7

To, co kompiluje się do niczego (zerowy ślad binarny), trafia do pliku nagłówkowego.

Zmienne nie kompilują się do niczego, ale deklaracje typów tak robią (ponieważ opisują one tylko zachowanie zmiennych).

funkcje nie, ale funkcje wbudowane (lub makra), ponieważ tworzą kod tylko wtedy, gdy są wywoływane.

szablony nie są kodem, są jedynie receptą na tworzenie kodu. więc są również umieszczane w plikach h.


1
„funkcje wbudowane ... tworzą kod tylko wtedy, gdy są wywoływane”. To nieprawda. Funkcje wbudowane mogą, ale nie muszą być wbudowane w witrynach wywołań, ale nawet jeśli są wbudowane, rzeczywista treść funkcji nadal istnieje, tak jak w przypadku funkcji nieliniowej. Powód, dla którego funkcje wbudowane w nagłówkach są w porządku, nie ma nic wspólnego z tym, czy generują one kod, ponieważ funkcje wbudowane nie wyzwalają reguły jednej definicji, więc w przeciwieństwie do funkcji nieliniowych nie ma problemów z łączeniem dwóch różnych jednostek tłumaczeniowych które zawierają oba nagłówki.
Steve Jessop

4

Ogólnie deklaracje umieszcza się w pliku nagłówkowym, a definicje w pliku implementacji (.cpp). Wyjątkiem są szablony, w przypadku których definicja musi również znajdować się w nagłówku.

To i podobne pytanie jest często zadawane w SO - zobacz Dlaczego pliki nagłówkowe i pliki .cpp są w C ++? i pliki nagłówkowe C ++, na przykład separacja kodu.


oczywiście możesz także umieścić definicje klas w plikach nagłówkowych. Nie muszą to być nawet szablony.
sellibitze

2

Deklaracje klas i funkcji, a także dokumentacja i definicje funkcji / metod wbudowanych (chociaż niektórzy wolą umieścić je w oddzielnych plikach .inl).


2

Głównie plik nagłówkowy zawiera szkielet lub deklarację klasy (nie zmienia się często)

a plik cpp zawiera implementację klasy (często się zmienia).


5
Prosimy o powstrzymanie się od niestandardowej terminologii. Co to jest „szkielet klasy”, co to jest „implementacja klasy”? Ponadto to, co nazywasz deklaracją w kontekście klas, prawdopodobnie zawiera definicje klas.
sellibitze

1

plik nagłówkowy (.h) powinien być przeznaczony na deklaracje klas, struktur i ich metod, prototypów itp. Implementacja tych obiektów odbywa się w cpp.

w .h

    class Foo {
    int j;

    Foo();
    Foo(int)
    void DoSomething();
}

1

Spodziewałbym się zobaczyć:

  • deklaracje
  • komentarze
  • definicje zaznaczone w tekście
  • szablony

prawdziwa odpowiedź brzmi jednak, czego nie należy umieszczać:

  • definicje (mogą prowadzić do wielokrotnego definiowania rzeczy)
  • używanie deklaracji / dyrektyw (wymusza je na każdym, w tym na twoim nagłówku, może powodować ukośniki nazw)

1
Z pewnością możesz umieścić definicje klas również w plikach nagłówkowych. Deklaracja klasy nie mówi nic na temat jej członków.
sellibitze

1

Nagłówek Definiuje coś ale nic nie mówi o implementacji. (Z wyłączeniem szablonów w tym „metaforze”.

Mając to na uwadze, musisz podzielić „definicje” na podgrupy, w tym przypadku istnieją dwa rodzaje definicji.

  • Definiujesz „układ” swojej struktury, podając tylko tyle, ile potrzeba otaczającym grupom użytkowników.
  • Definicje zmiennej, funkcji i klasy.

Teraz oczywiście mówię o pierwszej podgrupie.

Nagłówek jest po to, aby zdefiniować układ Twojej struktury, aby pomóc reszcie oprogramowania w użyciu implementacji. Możesz chcieć to potraktować jako „abstrakcję” swojej implementacji, co jest grzecznie powiedziane, ale myślę, że w tym przypadku pasuje całkiem dobrze.

Jak powiedzieli i pokazali poprzednie postery, deklarują prywatne i publiczne obszary użytkowania oraz ich nagłówki, obejmuje to również zmienne prywatne i publiczne. Nie chcę tutaj zajmować się projektowaniem kodu, ale możesz rozważyć, co umieścisz w swoich nagłówkach, ponieważ jest to warstwa między użytkownikiem końcowym a implementacją.


1
  • Pliki nagłówkowe - nie powinny zmieniać się zbyt często podczas programowania -> powinieneś pomyśleć i napisać je od razu (w idealnym przypadku)
  • Pliki źródłowe - zmiany w trakcie implementacji

To jest jedna praktyka. W przypadku niektórych mniejszych projektów może to być dobry sposób. Ale możesz spróbować wycofać funkcje i ich prototypy (w plikach nagłówkowych), zamiast zmieniać ich podpis lub je usuwać. Przynajmniej do zmiany numeru głównego. Tak jak podczas zmiany wersji 1.9.2 na wersję beta 2.0.0.
TamusJRoyce,

1

Nagłówek (.h)

  • Makra i dołączenia potrzebne do interfejsów (jak najmniej)
  • Deklaracja funkcji i klas
  • Dokumentacja interfejsu
  • Deklaracja funkcji / metod wbudowanych, jeśli istnieją
  • extern do zmiennych globalnych (jeśli istnieją)

Treść (.cpp)

  • Reszta makr i obejmuje
  • Dołącz nagłówek modułu
  • Definicja funkcji i metod
  • Zmienne globalne (jeśli istnieją)

Z reguły umieszcza się „współdzieloną” część modułu w .h (część, którą inne moduły muszą widzieć), a „nieudostępnioną” część w .cpp

PD: Tak, dołączyłem zmienne globalne. Używałem ich już kilka razy i ważne jest, aby nie definiować ich w nagłówkach, w przeciwnym razie otrzymasz wiele modułów, z których każdy będzie definiował własną zmienną.

EDYCJA: Zmodyfikowano po komentarzu Davida


Zasadą jest, że w pliku .h powinno znajdować się jak najmniej dołączeń, a plik .cpp powinien zawierać potrzebne nagłówki. To skraca czas kompilacji i nie zanieczyszcza przestrzeni nazw.
David Thornley
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.