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?
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?
Odpowiedzi:
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
, dll
lub .so
plikó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.
Zalety biblioteki tylko z nagłówkami:
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 #include
nagłó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 #include
potrzebnych 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.
detail
.
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ł.
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).
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)?