Nigdy tak naprawdę nie rozumiałem, dlaczego C ++ potrzebuje oddzielnego pliku nagłówkowego z tymi samymi funkcjami, co w pliku .cpp. Utrudnia to tworzenie klas i ich refaktoryzację, a także dodaje do projektu niepotrzebne pliki. A potem pojawia się problem z koniecznością dołączania plików nagłówkowych, ale z koniecznością jawnego sprawdzenia, czy zostały już uwzględnione.
C ++ został ratyfikowany w 1998 roku, dlaczego więc został zaprojektowany w ten sposób? Jakie zalety ma oddzielny plik nagłówkowy?
Pytanie uzupełniające:
W jaki sposób kompilator znajduje plik .cpp z zawartym w nim kodem, skoro wszystko, co dołączam, to plik .h? Czy zakłada, że plik .cpp ma taką samą nazwę jak plik .h, czy też faktycznie przegląda wszystkie pliki w drzewie katalogów?
c++
language-design
Marius
źródło
źródło
Odpowiedzi:
Wydaje się, że pytasz o oddzielenie definicji od deklaracji, chociaż istnieją inne zastosowania plików nagłówkowych.
Odpowiedź jest taka, że C ++ nie „potrzebuje” tego. Jeśli oznaczysz wszystko w linii (co i tak jest automatyczne dla funkcji składowych zdefiniowanych w definicji klasy), nie ma potrzeby separacji. Możesz po prostu zdefiniować wszystko w plikach nagłówkowych.
Powody, dla których warto się rozdzielić, to:
Jeśli twoje bardziej ogólne pytanie brzmi „dlaczego C ++ nie jest identyczne z Javą?”, To muszę zapytać „dlaczego piszesz C ++ zamiast Javy?” ;-p
Ale poważniej, powodem jest to, że kompilator C ++ nie może po prostu sięgnąć do innej jednostki tłumaczeniowej i dowiedzieć się, jak używać jej symboli w sposób, w jaki może to robić i robi javac. Plik nagłówkowy jest potrzebny do zadeklarowania kompilatorowi tego, czego może się spodziewać w czasie łączenia.
Podobnie
#include
jest z prostą substytucją tekstową. Jeśli zdefiniujesz wszystko w plikach nagłówkowych, preprocesor w końcu utworzy ogromną kopię i wklej każdy plik źródłowy w twoim projekcie i przekaże to do kompilatora. Fakt, że standard C ++ został ratyfikowany w 1998 roku, nie ma z tym nic wspólnego, to fakt, że środowisko kompilacji dla C ++ jest tak blisko oparte na środowisku C.Przekształcam moje komentarze, aby odpowiedzieć na pytanie uzupełniające:
Tak nie jest, przynajmniej nie w momencie kompilacji kodu używającego pliku nagłówkowego. Funkcje, z którymi się łączysz, nie muszą być jeszcze napisane, nieważne, że kompilator wie, w jakim
.cpp
pliku będą się znajdować. Wszystko, co kod wywołujący musi wiedzieć w czasie kompilacji, jest wyrażone w deklaracji funkcji. W momencie łączenia dostarczysz listę.o
plików lub bibliotek statycznych lub dynamicznych, a nagłówek w efekcie jest obietnicą, że definicje funkcji będą gdzieś tam.źródło
C ++ robi to w ten sposób, ponieważ C zrobił to w ten sposób, więc prawdziwe pytanie brzmi: dlaczego C zrobił to w ten sposób? Wikipedia trochę o tym mówi.
źródło
Niektórzy uważają pliki nagłówkowe za zaletę:
Ostatecznie system nagłówka jest artefaktem z lat 70-tych, kiedy zaprojektowano C. W tamtych czasach komputery miały bardzo mało pamięci, a przechowywanie całego modułu w pamięci po prostu nie wchodziło w grę. Kompilator musiał zacząć czytać plik od góry, a następnie przejść liniowo przez kod źródłowy. Umożliwia to mechanizm nagłówka. Kompilator nie musi brać pod uwagę innych jednostek tłumaczeniowych, musi po prostu czytać kod od góry do dołu.
C ++ zachował ten system ze względu na kompatybilność wsteczną.
Dziś to nie ma sensu. Jest nieefektywny, podatny na błędy i nadmiernie skomplikowany. Jeśli taki był cel, istnieją znacznie lepsze sposoby na oddzielenie interfejsu i implementacji .
Jednak jedną z propozycji dla C ++ 0x było dodanie odpowiedniego systemu modułowego, pozwalającego na kompilację kodu podobnego do .NET czy Java do większych modułów, wszystko za jednym razem i bez nagłówków. Ta propozycja nie odniosła sukcesu w C ++ 0x, ale uważam, że nadal znajduje się w kategorii „chcielibyśmy zrobić to później”. Być może w TR2 lub podobnym.
źródło
Według mojego (ograniczonego - normalnie nie jestem programistą C), jest to zakorzenione w C. Pamiętaj, że C nie wie, jakie są klasy lub przestrzenie nazw, to tylko jeden długi program. Ponadto funkcje muszą zostać zadeklarowane przed ich użyciem.
Na przykład poniższy kod powinien dać błąd kompilatora:
Błąd powinien być taki, że „SomeOtherFunction nie jest zadeklarowana”, ponieważ wywołujesz ją przed deklaracją. Jednym ze sposobów rozwiązania tego problemu jest przeniesienie SomeOtherFunction nad SomeFunction. Innym podejściem jest zadeklarowanie najpierw podpisu funkcji:
Dzięki temu kompilator wie: spójrz gdzieś w kodzie, jest funkcja o nazwie SomeOtherFunction, która zwraca wartość void i nie przyjmuje żadnych parametrów. Więc jeśli kodujesz kod, który próbuje wywołać SomeOtherFunction, nie panikuj i zamiast tego poszukaj go.
Teraz wyobraź sobie, że masz SomeFunction i SomeOtherFunction w dwóch różnych plikach .c. Następnie musisz #include „SomeOther.c” w Some.c. Teraz dodaj kilka funkcji „prywatnych” do SomeOther.c. Ponieważ C nie zna funkcji prywatnych, ta funkcja byłaby również dostępna w Some.c.
W tym miejscu pojawiają się pliki .h: Określają one wszystkie funkcje (i zmienne), które chcesz „wyeksportować” z pliku .c, do którego można uzyskać dostęp w innych plikach .c. W ten sposób zyskujesz coś w rodzaju zakresu publicznego / prywatnego. Możesz również przekazać ten plik .h innym osobom bez konieczności udostępniania swojego kodu źródłowego - pliki .h działają również na skompilowanych plikach .lib.
Tak więc głównym powodem jest wygoda, ochrona kodu źródłowego i trochę oddzielenia między częściami aplikacji.
To było jednak C. C ++ wprowadził klasy i modyfikatory prywatne / publiczne, więc chociaż nadal możesz zapytać, czy są potrzebne, C ++ AFAIK nadal wymaga deklaracji funkcji przed ich użyciem. Ponadto wielu programistów C ++ jest lub było również deweloperami C i przejęło swoje koncepcje i nawyki do C ++ - po co zmieniać to, co nie jest zepsute?
źródło
Pierwsza zaleta: jeśli nie masz plików nagłówkowych, musiałbyś dołączyć pliki źródłowe do innych plików źródłowych. Spowodowałoby to ponowne skompilowanie plików włączających po zmianie dołączonego pliku.
Druga zaleta: umożliwia udostępnianie interfejsów bez udostępniania kodu między różnymi jednostkami (różnymi programistami, zespołami, firmami itp.)
źródło
Potrzeba plików nagłówkowych wynika z ograniczeń, jakie ma kompilator, jeśli chodzi o informacje o typie funkcji i / lub zmiennych w innych modułach. Skompilowany program lub biblioteka nie zawiera informacji o typie wymaganych przez kompilator do powiązania z jakimikolwiek obiektami zdefiniowanymi w innych jednostkach kompilacji.
Aby zrekompensować to ograniczenie, C i C ++ zezwalają na deklaracje i te deklaracje mogą być dołączane do modułów, które ich używają za pomocą dyrektywy #include preprocesora.
Z drugiej strony języki takie jak Java lub C # zawierają informacje niezbędne do powiązania w danych wyjściowych kompilatora (plik klasy lub zestaw). W związku z tym nie ma już potrzeby utrzymywania samodzielnych deklaracji, które będą dołączane przez klientów modułu.
Przyczyna, dla której informacje o powiązaniu nie są uwzględniane w danych wyjściowych kompilatora, jest prosta: nie jest to potrzebne w czasie wykonywania (jakiekolwiek sprawdzanie typu odbywa się w czasie kompilacji). Po prostu marnowałoby miejsce. Pamiętaj, że C / C ++ pochodzi z czasów, gdy rozmiar pliku wykonywalnego lub biblioteki miał duże znaczenie.
źródło
C ++ został zaprojektowany w celu dodania nowoczesnych funkcji języka programowania do infrastruktury C, bez niepotrzebnych zmian w C, co nie dotyczyło samego języka.
Tak, w tym momencie (10 lat po pierwszym standardzie C ++ i 20 lat po tym, jak zaczął poważnie rosnąć w użyciu) łatwo jest zapytać, dlaczego nie ma odpowiedniego systemu modułowego. Oczywiście jakikolwiek nowy projektowany dziś język nie działałby jak C ++. Ale nie o to chodzi w C ++.
Celem C ++ jest ewolucja, płynna kontynuacja istniejącej praktyki, dodawanie tylko nowych możliwości bez (zbyt często) niszczenia rzeczy, które działają odpowiednio dla społeczności użytkowników.
Oznacza to, że niektóre rzeczy są trudniejsze (szczególnie dla osób rozpoczynających nowy projekt), a niektóre są łatwiejsze (zwłaszcza dla tych, którzy utrzymują istniejący kod) niż inne języki.
Więc zamiast oczekiwać, że C ++ zamieni się w C # (co byłoby bezcelowe, ponieważ mamy już C #), dlaczego nie wybrać odpowiedniego narzędzia do tego zadania? Sam staram się pisać znaczące fragmenty nowej funkcjonalności w nowoczesnym języku (tak się składa, że używam C #) i mam dużą ilość istniejącego C ++, które trzymam w C ++, ponieważ ponowne napisanie go nie miałoby żadnej wartości wszystko. Zresztą bardzo ładnie się integrują, więc jest to w dużej mierze bezbolesne.
źródło
Cóż, C ++ został ratyfikowany w 1998 roku, ale był używany znacznie dłużej, a ratyfikacja miała przede wszystkim na celu określenie bieżącego użycia, a nie narzucenie struktury. A ponieważ C ++ był oparty na C, a C ma pliki nagłówkowe, C ++ też je ma.
Głównym powodem tworzenia plików nagłówkowych jest umożliwienie oddzielnej kompilacji plików i zminimalizowanie zależności.
Powiedzmy, że mam foo.cpp i chcę użyć kodu z plików bar.h / bar.cpp.
Mogę #include "bar.h" w foo.cpp, a następnie zaprogramować i skompilować foo.cpp, nawet jeśli bar.cpp nie istnieje. Plik nagłówkowy stanowi obietnicę dla kompilatora, że klasy / funkcje z bar.h będą istniały w czasie wykonywania i zawiera już wszystko, co musi wiedzieć.
Oczywiście, jeśli funkcje w bar.h nie mają treści, kiedy próbuję połączyć mój program, to nie będzie się on łączyć i pojawi się błąd.
Efektem ubocznym jest to, że możesz udostępnić użytkownikom plik nagłówkowy bez ujawniania kodu źródłowego.
Innym jest to, że jeśli zmienisz implementację swojego kodu w pliku * .cpp, ale w ogóle nie zmienisz nagłówka, wystarczy skompilować plik * .cpp zamiast wszystkiego, co go używa. Oczywiście, jeśli umieścisz dużo implementacji w pliku nagłówkowym, stanie się to mniej przydatne.
źródło
Nie potrzebuje oddzielnego pliku nagłówkowego z tymi samymi funkcjami, co w main. Potrzebuje go tylko wtedy, gdy tworzysz aplikację przy użyciu wielu plików kodu i używasz funkcji, która nie została wcześniej zadeklarowana.
To naprawdę problem z zakresem.
źródło
W rzeczywistości pliki nagłówkowe stają się bardzo przydatne przy pierwszym badaniu programów, wyewidencjonowanie plików nagłówkowych (przy użyciu tylko edytora tekstu) daje przegląd architektury programu, w przeciwieństwie do innych języków, w których trzeba używać zaawansowanych narzędzi do przeglądania klas i ich członków.
źródło
Myślę, że prawdziwa (historyczna) Powodem plików nagłówkowych robił jak łatwiejsze dla twórców kompilatora ... ale potem, header pliki nie dają korzyści.
Sprawdź ten poprzedni post, aby uzyskać więcej dyskusji ...
źródło
Cóż, możesz doskonale rozwijać C ++ bez plików nagłówkowych. W rzeczywistości niektóre biblioteki, które intensywnie używają szablonów, nie używają paradygmatu plików nagłówka / kodu (patrz boost). Ale w C / C ++ nie można używać czegoś, co nie jest zadeklarowane. Jednym praktycznym sposobem radzenia sobie z tym jest użycie plików nagłówkowych. Ponadto zyskujesz przewagę współdzielenia interfejsu bez udostępniania kodu / implementacji. I myślę, że twórcy C nie przewidzieli tego: kiedy używasz współdzielonych plików nagłówkowych, musisz użyć słynnego:
nie jest to tak naprawdę funkcja języka, ale praktyczny sposób radzenia sobie z wielokrotnym włączaniem.
Tak więc myślę, że kiedy powstawało C, niedoceniane były problemy z deklaracją forward, a teraz, kiedy używamy języka wysokiego poziomu, takiego jak C ++, musimy sobie z tym radzić.
Kolejne obciążenie dla nas, biednych użytkowników C ++ ...
źródło
Jeśli chcesz, aby kompilator automatycznie wyszukiwał symbole zdefiniowane w innych plikach, musisz zmusić programistę do umieszczenia tych plików w predefiniowanych lokalizacjach (tak jak struktura pakietów Java określa strukturę folderów projektu). Wolę pliki nagłówkowe. Potrzebowałbyś także źródeł bibliotek, których używasz, lub jakiegoś jednolitego sposobu umieszczania informacji potrzebnych kompilatorowi w plikach binarnych.
źródło