Czy pliki nagłówkowe są naprawdę dobre? [Zamknięte]

20

Uważam, że pliki nagłówkowe są przydatne podczas przeglądania plików źródłowych C ++, ponieważ dają one „podsumowanie” wszystkich funkcji i elementów danych w klasie. Dlaczego tak wiele innych języków (takich jak Ruby, Python, Java itp.) Nie ma takiej funkcji? Czy jest to obszar, w którym przydaje się gadatliwość C ++?

Matt Fichman
źródło
Nie znam narzędzi w C i C ++, ale większość IDE / edytorów kodu, w których pracowałem, ma widok „struktury” lub „konturu”.
Michael K
1
Pliki nagłówkowe nie są dobre - są konieczne! - Zobacz także tę odpowiedź na stronie SO: stackoverflow.com/a/1319570/158483
Pete

Odpowiedzi:

31

Pierwotnym celem plików nagłówkowych było umożliwienie kompilacji jednoprzebiegowej i modułowości w C. Deklarowanie metod przed ich użyciem pozwoliło tylko na pojedyncze przejście kompilacji. Ten wiek już dawno minął, ponieważ nasze potężne komputery są w stanie wykonywać kompilacje wieloprzebiegowe bez żadnych problemów, a czasem nawet szybciej niż kompilatory C ++.

C ++ jest wstecznie kompatybilny z C, aby zachować pliki nagłówkowe, ale dodał wiele z nich, co spowodowało dość problematyczny projekt. Więcej w FQA .

W przypadku modułowości pliki nagłówkowe były potrzebne jako metadane dotyczące kodu w modułach. Na przykład. jakie metody (oraz w klasach C ++) są dostępne w jakiej bibliotece. Było oczywiste, że programista napisał to, ponieważ czas kompilacji był kosztowny. W dzisiejszych czasach kompilator nie generuje tych metadanych z samego kodu. Języki Java i .NET robią to normalnie.

Więc nie. Pliki nagłówkowe nie są dobre. To były czasy, kiedy wciąż mieliśmy kompilator i linker na osobnych dyskietkach, a kompilacja zajęła 30 minut. W dzisiejszych czasach przeszkadzają i są oznaką złego projektu.

Euforyk
źródło
8
Zgadzam się z większością tego, co piszesz, ale ostatni akapit upraszcza sprawę. Pliki nagłówkowe nadal służą użytecznym celom do definiowania publicznych interfejsów.
Benjamin Bannier
3
@honk Do tego właśnie doszło moje pytanie. Ale myślę, że współczesne IDE (jak sugeruje ElYusubov) w pewien sposób to negują.
Matt Fichman
5
Możesz dodać, że komitet C ++ pracuje nad modułami, dzięki którym C i C ++ będą mogły pracować bez żadnych nagłówków LUB z nagłówkami używanymi w bardziej wydajny sposób. To uczyniłoby tę odpowiedź bardziej sprawiedliwą.
Klaim
2
@MattFichman: Generowanie pliku nagłówkowego to jedno (co większość edytorów / IDE programistów może zrobić w jakiś sposób automatycznie), ale chodzi o publiczny interfejs API, że musi on zostać zdefiniowany i zaprojektowany przez rzeczywistego człowieka.
Benjamin Bannier
2
@honk Tak. Ale nie ma powodu, aby definiować to w osobnych plikach. Może mieć ten sam kod co implementacja. Tak jak robi to C #.
Euforia
17

Chociaż mogą być przydatne jako forma dokumentacji, system plików nagłówkowych jest wyjątkowo nieefektywny.

C został zaprojektowany tak, aby każdy przebieg kompilacji tworzył jeden moduł; każdy plik źródłowy jest kompilowany w osobnym uruchomieniu kompilatora. Z drugiej strony pliki nagłówkowe są wstrzykiwane do tego kroku kompilacji dla każdego pliku źródłowego, który się do nich odwołuje.

Oznacza to, że jeśli twój plik nagłówkowy jest zawarty w 300 plikach źródłowych, to jest analizowany i kompilowany w kółko, 300 razy, podczas tworzenia programu. Dokładnie to samo z tym samym rezultatem w kółko. Jest to ogromna strata czasu i jest jednym z głównych powodów, dla których tworzenie programów w językach C i C ++ trwa tak długo.

Wszystkie współczesne języki celowo unikają tej absurdalnej małej nieefektywności. Zamiast tego, zwykle w skompilowanych językach, niezbędne metadane są przechowywane w danych wyjściowych kompilacji, umożliwiając skompilowanemu plikowi działanie jako rodzaj odnośnika szybkiego wyszukiwania opisującego zawartość skompilowanego pliku. Wszystkie zalety pliku nagłówkowego, tworzonego automatycznie, bez dodatkowej pracy z Twojej strony.

Alternatywnie w tłumaczonych językach, każdy ładowany moduł pozostaje w pamięci. Odwołanie lub dołączenie lub wymaganie biblioteki spowoduje odczytanie i skompilowanie powiązanego kodu źródłowego, który pozostaje rezydentny aż do zakończenia programu. Jeśli potrzebujesz go również w innym miejscu, nie musisz wykonywać żadnej dodatkowej pracy, ponieważ został już załadowany.

W obu przypadkach można „przeglądać” dane utworzone w tym kroku za pomocą narzędzi języka. Zazwyczaj IDE będzie miało przeglądarkę klas. A jeśli język ma REPL, można go często użyć do wygenerowania podsumowania dokumentacji załadowanych obiektów.

tylerl
źródło
5
nie do końca prawda - większość, jeśli nie wszystkie, kompilatory wstępnie kompilują nagłówki tak, jak je widzą, więc włączenie ich do następnej jednostki kompilacji nie jest gorsze niż znalezienie bloku wstępnie skompilowanego kodu. Nie różni się niczym od, powiedzmy, kodu .NET wielokrotnie „budującego” system.data.types dla każdego kompilowanego pliku C #. Powodem, dla którego programy C / C ++ są tak długie, jest fakt, że są one kompilowane (i optymalizowane). Wszystko, czego potrzebuje program .NET, to parsowanie do IL, środowisko wykonawcze dokonuje rzeczywistej kompilacji za pośrednictwem JIT. To powiedziawszy, 300 plików źródłowych w kodzie C # również zajmuje sporo czasu.
gbjbaanb
7

Nie jest jasne, co masz na myśli, przeglądając plik w poszukiwaniu funkcji i członków danych. Jednak większość IDE zapewnia narzędzia do przeglądania klasy i przeglądania członków danych klasy.

Na przykład: Visual Studio ma Class Viewi Object browserładnie zapewnia żądaną funkcjonalność. Jak w poniższych zrzutach ekranu.

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

Jusubow
źródło
4

Dodatkową wadą plików nagłówkowych jest to, że program zależy od kolejności ich włączenia, a programista musi podjąć dodatkowe kroki w celu zapewnienia poprawności.

To w połączeniu z systemem makro C (odziedziczonym przez C ++) prowadzi do wielu pułapek i mylących sytuacji.

Aby to zilustrować, jeśli jeden nagłówek zdefiniował jakiś symbol za pomocą makr do jego użycia, a inny nagłówek użył symbolu w inny sposób, na przykład nazwę funkcji, wówczas kolejność uwzględnienia miałaby duży wpływ na końcowy wynik. Istnieje wiele takich przykładów.

jcora
źródło
2

Zawsze lubiłem pliki nagłówkowe, ponieważ zapewniają one interfejs do implementacji, wraz z dodatkowymi informacjami, takimi jak zmienne klas, wszystko w jednym łatwym do przeglądania pliku.

Widzę dużo kodu C # (który nie wymaga 2 plików na klasę) napisanych przy użyciu 2 plików na klasę - jeden z rzeczywistą implementacją klasy, a drugi z interfejsem. Ten projekt jest dobry do kpienia (niezbędny w niektórych systemach) i pomaga zdefiniować dokumentację klasy bez konieczności używania IDE do przeglądania skompilowanych metadanych. Poszedłem tak daleko, by powiedzieć, że to dobra praktyka.

Więc C / C ++ nakazujący ekwiwalent (swego rodzaju) interfejsu w plikach nagłówkowych jest dobrą rzeczą.

Wiem, że istnieją zwolennicy innych systemów, którzy ich nie lubią z takich powodów, jak: „trudniej jest po prostu zhakować kod, jeśli trzeba umieścić rzeczy w 2 plikach”, ale moje podejście jest takie, że samo hakowanie kodu i tak nie jest dobrą praktyką , a kiedy zaczniesz pisać / projektować kod z odrobiną przemyślenia, wtedy oczywiście będziesz definiować nagłówki / interfejsy.

gbjbaanb
źródło
Interfejs i implementacja w różnych plikach? Jeśli potrzebujesz tego zgodnie z projektem, nadal bardzo powszechne jest definiowanie interfejsu (lub klasy abstrakcyjnej) w językach takich jak Java / C # w jednym pliku i ukrywanie implementacji w innych plikach. Nie wymaga to starych i trudnych plików nagłówkowych.
Petter Nordlander,
co? to nie to, co powiedziałem - interfejs i implementację klasy definiuje się w 2 plikach.
gbjbaanb
2
cóż, nie wymaga to plików nagłówkowych i nie poprawia plików nagłówkowych. Tak, są one potrzebne w językach z dziedzictwem, ale jako koncepcja nie są potrzebne do dzielenia interfejsu / implementacji na różne pliki (w rzeczywistości pogarszają).
Petter Nordlander
@Petter Myślę, że źle zrozumiałeś, koncepcyjnie nie są one różne - istnieje nagłówek zapewniający interfejs w języku C, i chociaż masz rację - możesz połączyć 2 (jak wiele z kodem C tylko w nagłówku), to nie jest najlepsze praktyki, które widziałem w wielu miejscach. Na przykład wszyscy deweloperzy C # widzieli interfejs odseparowany od klasy. Dobrą praktyką jest robienie tego, ponieważ wtedy możesz dołączyć plik interfejsu do wielu innych plików bez zanieczyszczenia. Teraz, jeśli uważasz, że ten podział jest najlepszą praktyką (tak jest), to sposobem na osiągnięcie tego w C jest użycie plików nagłówkowych.
gbjbaanb
Gdyby nagłówki były rzeczywiście przydatne do oddzielenia interfejsu i implementacji, takie rzeczy jak PImpl nie byłyby potrzebne, aby ukryć prywatne komponenty i oddzielić użytkowników końcowych od konieczności ponownej kompilacji z nimi. Zamiast tego otrzymujemy najgorsze z obu światów. Nagłówki udają szybko oddaloną fasadę separacji, jednocześnie otwierając niezliczoną liczbę pułapek, które wymagają szczegółowego kodu, jeśli chcesz obejść je.
underscore_d
1

Powiedziałbym, że pliki nagłówkowe nie są świetne, ponieważ mętnią interfejs i implementację. Ogólnym celem programowania, a zwłaszcza OOP, jest zdefiniowanie interfejsu i ukrycie szczegółów implementacji, ale plik nagłówkowy C ++ pokazuje zarówno metody, dziedziczenie, jak i elementy publiczne (interfejs), a także metody prywatne i elementy prywatne (część wdrożenia). Nie wspominając o tym, że w niektórych przypadkach wstawiasz kod lub konstruktory w pliku nagłówkowym, a niektóre biblioteki zawierają kod szablonu w nagłówkach, który naprawdę miesza implementację z interfejsem.

Uważam, że pierwotnym celem było umożliwienie kodowi korzystania z innych bibliotek, obiektów itp. Bez konieczności importowania całej zawartości skryptu. Wystarczy nagłówek, aby skompilować i połączyć. Oszczędza w ten sposób czas i cykle. W takim przypadku jest to przyzwoity pomysł, ale jest to tylko jeden ze sposobów rozwiązania tych problemów.

Jeśli chodzi o przeglądanie struktury programu, większość IDE zapewnia taką możliwość, a istnieje wiele narzędzi, które wykopią interfejsy, przeprowadzą analizę kodu, dekompilację itp., Abyś mógł zobaczyć, co dzieje się pod okładkami.

A dlaczego inne języki nie implementują tej samej funkcji? Cóż, ponieważ inne języki pochodzą od innych ludzi, a ci projektanci / twórcy mają inną wizję tego, jak powinny działać.

Najlepszą odpowiedzią jest trzymanie się zadania, które musisz wykonać, i sprawia, że ​​jesteś szczęśliwy.

Maurice Reeves
źródło
0

W wielu językach programowania, gdy program jest podzielony na wiele jednostek kompilacji, kod w jednej jednostce będzie mógł używać rzeczy zdefiniowanych w innym, tylko jeśli kompilator ma pewne środki, aby wiedzieć, co to jest. Zamiast wymagać, aby kompilator przetwarzający jedną jednostkę kompilacji sprawdzał cały tekst każdej jednostki kompilacji, która definiuje wszystko używane w bieżącej jednostce, najlepiej, aby kompilator odbierał, podczas przetwarzania każdej jednostki, pełny tekst jednostki, która ma być kompilowana z niewielką ilością informacji od wszystkich innych.

W C programista jest odpowiedzialny za utworzenie dwóch plików dla każdej jednostki - jeden zawierający informacje, które będą potrzebne tylko podczas kompilacji tej jednostki, a drugi zawierający informacje, które będą również potrzebne podczas kompilacji innych jednostek. Jest to dość paskudny, zhackowany projekt, ale pozwala uniknąć potrzeby, aby standard językowy określał wszystko, w jaki sposób kompilatory powinny generować i przetwarzać pliki pośrednie. Wiele innych języków stosuje inne podejście, w którym kompilator generuje z każdego pliku źródłowego plik pośredni opisujący wszystko w tym pliku źródłowym, który powinien być dostępny dla kodu zewnętrznego. Takie podejście pozwala uniknąć konieczności powielania informacji w dwóch plikach źródłowych, ale wymaga, aby platforma kompilacji zdefiniowała semantykę plików w sposób, który nie jest wymagany dla C.

supercat
źródło