Dlaczego C ++ ma pliki nagłówkowe i .cpp?
c++
header-files
Peter Mortensen
źródło
źródło
Odpowiedzi:
Głównym powodem byłoby oddzielenie interfejsu od implementacji. Nagłówek deklaruje „co” zrobi klasa (lub cokolwiek, co jest implementowane), podczas gdy plik cpp określa „jak” będzie wykonywać te funkcje.
Zmniejsza to zależności, dzięki czemu kod wykorzystujący nagłówek niekoniecznie musi znać wszystkie szczegóły implementacji i inne klasy / nagłówki potrzebne tylko do tego. Skróci to czas kompilacji, a także ilość ponownej kompilacji potrzebnej, gdy coś w implementacji zmieni się.
To nie jest idealne i zwykle stosujesz takie techniki, jak Pimpl Idiom, aby odpowiednio oddzielić interfejs i implementację, ale to dobry początek.
źródło
Kompilacja w C ++
Kompilacja w C ++ odbywa się w 2 głównych fazach:
Pierwszą jest kompilacja „źródłowych” plików tekstowych w binarne pliki „obiektowe”: plik CPP jest plikiem skompilowanym i jest kompilowany bez wiedzy o innych plikach CPP (lub nawet bibliotekach), chyba że zostanie do niego dostarczony poprzez surową deklarację lub włączenie nagłówka. Plik CPP jest zwykle kompilowany do pliku „OB ”lub .OBJ.
Drugim jest połączenie wszystkich plików „obiektowych”, a tym samym utworzenie końcowego pliku binarnego (biblioteki lub pliku wykonywalnego).
Gdzie HPP pasuje do tego procesu?
Słaby samotny plik CPP ...
Kompilacja każdego pliku CPP jest niezależna od wszystkich innych plików CPP, co oznacza, że jeśli A.CPP potrzebuje symbolu zdefiniowanego w B.CPP, takiego jak:
Nie skompiluje się, ponieważ A.CPP nie ma sposobu, aby wiedzieć, że istnieje „doSomethingElse”… Chyba że w A.CPP istnieje deklaracja, taka jak:
Następnie, jeśli masz C.CPP, który używa tego samego symbolu, to skopiuj / wklej deklarację ...
KOPIUJ / WKLEJ ALERT!
Tak, jest problem. Kopiowanie / wklejanie jest niebezpieczne i trudne w utrzymaniu. Co oznacza, że byłoby fajnie, gdybyśmy mieli sposób NIE kopiować / wklejać i nadal deklarować symbol ... Jak możemy to zrobić? Przez dołączenie jakiegoś pliku tekstowego, który jest zwykle dodawany przez .h, .hxx, .h ++ lub, mój preferowany dla plików C ++, .hpp:
Jak
include
działaDołączenie pliku w istocie parsuje, a następnie kopiuje i wkleja jego zawartość do pliku CPP.
Na przykład w następującym kodzie z nagłówkiem A.HPP:
... źródło B.CPP:
... stanie się po włączeniu:
Jedna mała rzecz - dlaczego warto włączyć B.HPP w B.CPP?
W obecnym przypadku nie jest to konieczne, a B.HPP ma
doSomethingElse
deklarację funkcji, a B.CPP madoSomethingElse
definicję funkcji (która sama w sobie jest deklaracją). Ale w bardziej ogólnym przypadku, gdy B.HPP jest używany do deklaracji (i kodu wbudowanego), może nie istnieć odpowiednia definicja (na przykład wyliczenia, zwykłe struktury itp.), Więc włączenie może być potrzebne, jeśli B.CPP korzysta z deklaracji B.HPP. Podsumowując, „dobrym gustem” jest, aby źródło domyślnie zawierało nagłówek.Wniosek
Plik nagłówkowy jest zatem konieczny, ponieważ kompilator C ++ nie jest w stanie samodzielnie szukać deklaracji symboli, dlatego należy pomóc, włączając te deklaracje.
Ostatnie słowo: powinieneś umieścić osłony nagłówków wokół zawartości plików HPP, aby mieć pewność, że wiele inkluzji niczego nie zepsuje, ale w sumie uważam, że główny powód istnienia plików HPP został wyjaśniony powyżej.
lub nawet prościej
źródło
You still have to copy paste the signature from header file to cpp file, don't you?
Nie ma potrzeby. Tak długo, jak CPP „zawiera” HPP, prekompilator będzie automatycznie kopiował i wklejał zawartość pliku HPP do pliku CPP. Zaktualizowałem odpowiedź, aby to wyjaśnić.While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself
. Nie, nie ma. Zna tylko typy podane przez użytkownika, co w połowie czasu nie będzie nawet przeszkadzało w odczytaniu zwracanej wartości. Następnie zachodzą niejawne konwersje. A kiedy masz kod:foo(bar)
nie możesz nawet być pewien, żefoo
jest funkcją. Zatem kompilator musi mieć dostęp do informacji w nagłówkach, aby zdecydować, czy źródło kompiluje się poprawnie, czy nie ... Następnie, po skompilowaniu kodu, linker po prostu połączy połączenia funkcji.Seems, they're just a pretty ugly arbitrary design.
: Gdyby C ++ został stworzony w 2012 roku. Ale pamiętaj, że C ++ został zbudowany na C w latach 80., a wówczas ograniczenia były w tym czasie zupełnie inne (IIRC, dla celów adopcji, postanowiono zachować te same linkery niż C).foo(bar)
jest to funkcja - jeśli jest uzyskiwana jako wskaźnik? Mówiąc o złym projekcie, obwiniam C, a nie C ++. Naprawdę nie lubię niektórych ograniczeń czystego C, takich jak pliki nagłówkowe lub funkcje zwracające jedną i tylko jedną wartość, jednocześnie przyjmując wiele argumentów na wejściu (nie wydaje się naturalne, aby wejścia i wyjścia zachowywały się w podobny sposób ; dlaczego wiele argumentów, ale pojedyncze wyjście?) :)Why can't I be sure, that foo(bar) is a function
foo może być typem, więc można by wywołać konstruktora klasy.In fact, speaking of bad design, I blame C, not C++
: Mogę obwiniać C za wiele rzeczy, ale bycie zaprojektowanym w latach 70. nie będzie jedną z nich. Ponownie, ograniczenia tego czasu ...such as having header files or having functions return one and only one value
: Krotki mogą pomóc to złagodzić, a także przekazywać argumenty przez referencję. Jaka byłaby składnia, aby odzyskać wiele wartości i czy warto byłoby zmienić język?Ponieważ C, skąd narodziła się koncepcja, ma 30 lat, a wtedy był to jedyny możliwy sposób na połączenie kodu z wielu plików.
Dzisiaj jest to okropny hack, który całkowicie niszczy czas kompilacji w C ++, powoduje niezliczone niepotrzebne zależności (ponieważ definicje klas w pliku nagłówkowym ujawniają zbyt wiele informacji o implementacji) i tak dalej.
źródło
Ponieważ w C ++ końcowy kod wykonywalny nie zawiera żadnych informacji o symbolach, jest to mniej więcej czysty kod maszynowy.
Dlatego potrzebujesz sposobu na opisanie interfejsu fragmentu kodu, który jest niezależny od samego kodu. Ten opis znajduje się w pliku nagłówkowym.
źródło
Ponieważ C ++ odziedziczył je po C. Niestety.
źródło
Ponieważ ludzie, którzy zaprojektowali format biblioteki, nie chcieli „marnować” miejsca na rzadko używane informacje, takie jak makra preprocesora C i deklaracje funkcji.
Ponieważ potrzebujesz tych informacji, aby poinformować kompilator „ta funkcja jest dostępna później, gdy linker wykonuje swoją pracę”, musieli wymyślić drugi plik, w którym mogłyby być przechowywane te wspólne informacje.
Większość języków po C / C ++ zapisuje te informacje w danych wyjściowych (na przykład kod bajtowy Java) lub w ogóle nie używają formatu prekompilowanego, zawsze są dystrybuowane w formie źródłowej i kompilowane w locie (Python, Perl).
źródło
Jest to preprocesorowy sposób deklarowania interfejsów. Interfejs (deklaracje metod) umieszczasz w pliku nagłówkowym, a implementację w cpp. Aplikacje korzystające z Twojej biblioteki muszą znać interfejs, do którego mogą uzyskać dostęp poprzez #include.
źródło
Często będziesz chciał mieć definicję interfejsu bez konieczności wysyłania całego kodu. Na przykład, jeśli masz bibliotekę współdzieloną, dostarczasz z nią plik nagłówka, który definiuje wszystkie funkcje i symbole używane w bibliotece współdzielonej. Bez plików nagłówkowych należy wysłać źródło.
W ramach jednego projektu wykorzystywane są pliki nagłówkowe IMHO do co najmniej dwóch celów:
źródło
Odpowiadając na odpowiedź MadKeithV ,
Innym powodem jest to, że nagłówek nadaje unikalny identyfikator każdej klasie.
Więc jeśli mamy coś takiego
Podczas próby zbudowania projektu wystąpią błędy, ponieważ A jest częścią B, dzięki nagłówkom uniknęlibyśmy tego rodzaju bólu głowy ...
źródło