Zalety bibliotek obsługujących tylko nagłówki


106

Jakie są zalety biblioteki z samym nagłówkiem i dlaczego miałbyś napisać ją w ten sposób, a nie umieszczać implementacji w oddzielnym pliku?


Głównie szablony, ale ułatwi to także dystrybucję i używanie.
BoBTFish

4
Chciałbym dodać wady biblioteki z samym nagłówkiem do zakresu pytania ...
moooeeeep

Jakie są wady, o których jeszcze nie wspomniano?
NebulaFox

7
@moooeeeep: z powodu wad możesz przeczytać akapit „Zatrzymaj wstawianie kodu” na stronie C ++ Dos and Don'ts Chromium Projects.
Pan C64

Odpowiedzi:


58

Istnieją sytuacje, w których biblioteka z samym nagłówkiem jest jedyną opcją, na przykład podczas obsługi szablonów.

Posiadanie biblioteki z samym nagłówkiem oznacza również, że nie musisz martwić się o różne platformy, na których może być używana biblioteka. Oddzielając implementację, zwykle robi się to, aby ukryć szczegóły implementacji i rozpowszechniać bibliotekę jako kombinację nagłówków i bibliotek ( lib, dlllub .soplików). Muszą one oczywiście zostać skompilowane dla wszystkich różnych systemów operacyjnych / wersji, które oferujesz.

Możesz również rozpowszechniać pliki implementacji, ale oznaczałoby to dodatkowy krok dla użytkownika - kompilację biblioteki przed jej użyciem.

Oczywiście dotyczy to indywidualnych przypadków . Na przykład czasami wzrasta liczba bibliotek obsługujących tylko nagłówkikod rozmiar & czasy kompilacji.


7
„Posiadanie biblioteki obsługującej tylko nagłówki oznacza również, że nie musisz martwić się o różne platformy, na których biblioteka może być używana”: tylko wtedy, gdy nie musisz utrzymywać biblioteki. W przeciwnym razie jest to koszmar, z raportami błędów, których nie można odtworzyć ani przetestować na posiadanym materiale.
James Kanze

1
Właśnie zadałem podobne pytanie dotyczące korzyści związanych z wydajnością samego nagłówka. Jak widać, nie ma różnicy w rozmiarze kodu. Jednak przykładowa implementacja tylko z nagłówkiem działała o 7% wolniej. stackoverflow.com/questions/12290639/…
Homer6

@ Homer6 dzięki za pingowanie mnie. Nigdy tego nie mierzyłem.
Luchian Grigore

2
@LuchianGrigore Nie mogłem znaleźć nikogo, kto też miałby. Dlatego odpowiedź zajęła trochę czasu. Jest tak wiele spekulatywnych komentarzy dotyczących „zwiększenia rozmiaru kodu” i „zużycia pamięci”. W końcu mam migawkę różnic, nawet jeśli to tylko jeden przykład.
Homer6

@ Homer6. Dlaczego nie miałoby to zwiększyć rozmiaru kodu? Zakładając, że tworzysz wiele bibliotek, które używają tylko nagłówka lib, a następnie Twoja aplikacja używa wszystkich tych bibliotek, które musisz mieć wiele kopii, w przeciwieństwie do łączenia z jedną biblioteką współdzieloną.
pooya13

61

Zalety biblioteki tylko z nagłówkami:

  • Upraszcza proces budowania. Nie musisz budować biblioteki i nie musisz określać skompilowanej biblioteki podczas etapu łączenia kompilacji. Jeśli masz skompilowaną bibliotekę, prawdopodobnie będziesz chciał zbudować jej wiele wersji: jedną skompilowaną z włączonym debugowaniem, inną z włączoną optymalizacją i prawdopodobnie jeszcze jedną pozbawioną symboli. A może nawet więcej w przypadku systemu wieloplatformowego.

Wady biblioteki obsługującej tylko nagłówki:

  • Większe pliki obiektów. Każda metoda wbudowana z biblioteki, która jest używana w jakimś pliku źródłowym, również otrzyma słaby symbol, definicję out-of-line w skompilowanym pliku obiektowym dla tego pliku źródłowego. Spowalnia to kompilator, a także spowalnia konsolidator. Kompilator musi wygenerować cały ten nadmiar, a następnie linker musi to odfiltrować.

  • Dłuższa kompilacja. Oprócz wspomnianego powyżej problemu z nadmiarem, kompilacja potrwa dłużej, ponieważ nagłówki są z natury większe w przypadku biblioteki zawierającej tylko nagłówki niż w przypadku biblioteki skompilowanej. Te duże nagłówki będą musiały zostać przeanalizowane dla każdego pliku źródłowego używającego biblioteki. Innym czynnikiem jest to, że te pliki nagłówkowe w bibliotece tylko z #includenagłówkami muszą zawierać nagłówki wymagane przez definicje wbudowane, a także nagłówki, które byłyby potrzebne, gdyby biblioteka została zbudowana jako biblioteka skompilowana.

  • Bardziej zagmatwana kompilacja. Otrzymujesz o wiele więcej zależności z biblioteką tylko z nagłówkiem z powodu tych dodatkowych #includepotrzebnych w przypadku biblioteki tylko z nagłówkiem. Zmień implementację jakiejś kluczowej funkcji w bibliotece, a może zajść potrzeba ponownej kompilacji całego projektu. Wprowadź tę zmianę w pliku źródłowym dla skompilowanej biblioteki, a wszystko, co musisz zrobić, to przekompilować ten jeden plik źródłowy biblioteki, zaktualizować skompilowaną bibliotekę tym nowym plikiem .o i ponownie połączyć aplikację.

  • Trudniejsze do czytania dla człowieka. Nawet mając najlepszą dokumentację, użytkownicy biblioteki często muszą uciekać się do czytania nagłówków biblioteki. Nagłówki w bibliotece zawierającej tylko nagłówki są wypełnione szczegółami implementacji, które przeszkadzają w zrozumieniu interfejsu. W skompilowanej bibliotece widzisz tylko interfejs i krótki komentarz na temat tego, co robi implementacja, a to zwykle wszystko, czego chcesz. To naprawdę wszystko, czego powinieneś chcieć. Nie powinieneś znać szczegółów implementacji, aby wiedzieć, jak korzystać z biblioteki.


23
Ostatni punkt nie ma sensu. Każda rozsądna dokumentacja będzie zawierać deklarację funkcji, parametry, wartości zwracane itp. Oraz wszystkie powiązane komentarze. Jeśli musisz odwołać się do pliku nagłówkowego, dokumentacja nie powiodła się.
Thomas

6
@Thomas - Nawet w przypadku najlepszych profesjonalnych bibliotek często muszę uciekać się do czytania nagłówka „w porządku”. W rzeczywistości, jeśli tak zwana „dobra” dokumentacja jest wyodrębniona z kodu i komentarza, zazwyczaj lubię czytać nagłówki. Kod plus komentarze mówią mi więcej niż automatycznie generowana dokumentacja.
David Hammen,

2
Ostatni punkt jest nieprawidłowy. Nagłówki są już wypełnione szczegółami implementacji w prywatnych elementach członkowskich, więc nie jest tak, że plik cpp ukrywa wszystkie szczegóły implementacji. Ponadto języki takie jak C # są zasadniczo „tylko nagłówkami” z założenia, a środowisko IDE dba o zaciemnianie szczegółów („składanie” ich w dół)
Mark Lakata,

2
@Tomas: Zgadzam się, ostatni punkt jest całkowicie fałszywy. Możesz łatwo zachować interfejs i implementację tak samo oddzielnie, korzystając z bibliotek obsługujących tylko nagłówki; po prostu masz nagłówek interfejsu # zawiera szczegóły implementacji. Właśnie dlatego biblioteki Boost zazwyczaj zawierają podkatalog (i przestrzeń nazw) o nazwie detail.
Nemo

4
@Thomas: Nie zgadzam się. Plik nagłówkowy jest generalnie pierwszym miejscem, do którego udaję się w celu uzyskania dokumentacji. Jeśli nagłówek jest dobrze napisany, często nie ma potrzeby zewnętrznej dokumentacji.
Joel Cornett,

15

Wiem, że to stary wątek, ale nikt nie wspomniał o interfejsach ABI ani o konkretnych problemach z kompilatorem. Więc pomyślałem, że tak.

Zasadniczo opiera się to na koncepcji, w której albo piszesz bibliotekę z nagłówkiem, aby rozpowszechniać ją wśród ludzi, albo ponownie wykorzystujesz siebie, a nie masz wszystko w nagłówku. Jeśli myślisz o ponownym użyciu nagłówka i plików źródłowych i ponownej kompilacji ich w każdym projekcie, to tak naprawdę nie ma to zastosowania.

Zasadniczo, jeśli skompilujesz swój kod C ++ i zbudujesz bibliotekę za pomocą jednego kompilatora, a następnie użytkownik spróbuje użyć tej biblioteki z innym kompilatorem lub inną wersją tego samego kompilatora, możesz otrzymać błędy konsolidatora lub dziwne zachowanie w czasie wykonywania z powodu niezgodności plików binarnych.

Na przykład dostawcy kompilatorów często zmieniają implementację STL między wersjami. Jeśli masz w bibliotece funkcję, która akceptuje std :: vector, to oczekuje, że bajty w tej klasie będą ułożone w sposób, w jaki zostały ułożone podczas kompilacji biblioteki. Jeśli w nowej wersji kompilatora dostawca wprowadził ulepszenia wydajności do std :: vector, kod użytkownika widzi nową klasę, która może mieć inną strukturę, i przekazuje tę nową strukturę do biblioteki. Stamtąd wszystko idzie w dół ... Dlatego nie zaleca się przekazywania obiektów STL poza granice biblioteki. To samo dotyczy typów C Run-Time (CRT).

Mówiąc o CRT, twoja biblioteka i kod źródłowy użytkownika generalnie muszą być połączone z tym samym CRT. W programie Visual Studio, jeśli tworzysz bibliotekę przy użyciu Multithreaded CRT, ale użytkownik łączy się z Multithreaded Debug CRT, wystąpią problemy z łączem, ponieważ biblioteka może nie znaleźć potrzebnych symboli. Nie pamiętam, która to była funkcja, ale dla Visual Studio 2015 Microsoft wprowadził jedną wbudowaną funkcję CRT. Nagle znalazło się w nagłówku, a nie w bibliotece CRT, więc biblioteki, które spodziewały się go znaleźć w czasie łączenia, nie mogły już tego zrobić, a to wygenerowało błędy łącza. W rezultacie te biblioteki wymagały ponownej kompilacji z Visual Studio 2015.

Możesz również uzyskać błędy łącza lub dziwne zachowanie, jeśli używasz interfejsu API systemu Windows, ale tworzysz z innymi ustawieniami Unicode dla użytkownika biblioteki. Dzieje się tak, ponieważ interfejs API systemu Windows ma funkcje, które używają ciągów Unicode lub ASCII oraz makr / definicji, które automagicznie używają poprawnych typów w oparciu o ustawienia Unicode projektu. Jeśli przekażesz ciąg znaków przez granicę biblioteki, który jest niewłaściwego typu, wszystko zepsuje się w czasie wykonywania. Lub może się okazać, że program nie łączy się w pierwszej kolejności.

Te rzeczy są również prawdziwe w przypadku przekazywania obiektów / typów przez granice bibliotek z bibliotek innych firm (np. Wektor Eigen lub macierz GSL). Jeśli biblioteka innej firmy zmieni swój nagłówek między kompilacją biblioteki a użytkownikiem kompilującym kod, sytuacja się zepsuje.

Zasadniczo, aby być bezpiecznym, jedynymi rzeczami, które można przekazać poza granice biblioteki, są typy i zwykłe stare dane (POD). W idealnym przypadku każdy POD powinien mieć strukturę zdefiniowaną w Twoich własnych nagłówkach i nie opierać się na nagłówkach innych firm.

Jeśli podasz bibliotekę tylko z nagłówkiem, cały kod zostanie skompilowany z tymi samymi ustawieniami kompilatora i z tymi samymi nagłówkami, więc wiele z tych problemów zniknie (pod warunkiem, że wersja trzeciej częściowej biblioteki, której Ty i Twój użytkownik używa, jest zgodna z API).

Istnieją jednak wady, o których wspomniano powyżej, takie jak wydłużony czas kompilacji. Możesz także prowadzić firmę, więc możesz nie chcieć przekazywać wszystkich szczegółów implementacji kodu źródłowego wszystkim użytkownikom na wypadek, gdyby któryś z nich go ukradł.


8

Główną „korzyścią” jest to, że wymaga dostarczenia kodu źródłowego, więc będziesz otrzymywać raporty o błędach na maszynach i kompilatorach, o których nigdy nie słyszałeś. Gdy biblioteka składa się w całości z szablonów, nie masz dużego wyboru, ale kiedy masz wybór, sam nagłówek jest zwykle kiepskim wyborem inżynieryjnym. (Z drugiej strony oczywiście nagłówek oznacza tylko, że nie musisz dokumentować żadnej procedury integracji).


0

Inlining można wykonać dzięki optymalizacji czasu łącza (LTO)

Chciałbym to podkreślić, ponieważ zmniejsza to wartość jednej z dwóch głównych zalet bibliotek tylko z nagłówkiem: „potrzebujesz definicji w nagłówku, aby wstawić je w tekście”.

Minimalny konkretny przykład tego przedstawiono pod adresem: Optymalizacja czasu łącza i inline

Po prostu przekazujesz flagę, a wstawianie może być wykonywane między plikami obiektowymi bez żadnej pracy nad refaktoryzacją, nie ma już potrzeby przechowywania definicji w nagłówkach.

LTO może mieć jednak również swoje wady: czy istnieje powód, dla którego nie należy stosować optymalizacji czasu łącza (LTO)?

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.