Vorteile von Nur-Header-Bibliotheken

97

Was sind die Vorteile einer Nur-Header-Bibliothek und warum sollten Sie sie so schreiben, dass die Implementierung nicht in eine separate Datei gestellt wird?

NebulaFox
quelle
Meistens Vorlagen, aber es wird auch ein bisschen einfacher zu verteilen und zu verwenden.
BoBTFish
4
Ich möchte die Nachteile einer Nur-Header-Bibliothek zum Umfang der Frage
hinzufügen
Welche Nachteile gibt es, die noch nicht erwähnt wurden?
NebulaFox
7
@moooeeeep: Für die Nachteile möchten Sie möglicherweise den Absatz "Stop Inlining Code" auf der Webseite von C ++ Dos and Don'ts Chromium Projects lesen .
Mr.C64

Antworten:

57

Es gibt Situationen, in denen eine Nur-Header-Bibliothek die einzige Option ist, beispielsweise beim Umgang mit Vorlagen.

Wenn Sie nur eine Header-Bibliothek haben, müssen Sie sich keine Gedanken über verschiedene Plattformen machen, auf denen die Bibliothek möglicherweise verwendet wird. Wenn Sie die Implementierung trennen, verbergen Sie normalerweise Implementierungsdetails und verteilen die Bibliothek als Kombination aus Headern und Bibliotheken ( lib, dll's oder .soDateien). Diese müssen natürlich für alle verschiedenen Betriebssysteme / Versionen kompiliert werden, die Sie unterstützen.

Sie könnten die Implementierungsdateien auch verteilen, dies würde jedoch einen zusätzlichen Schritt für den Benutzer bedeuten - das Kompilieren Ihrer Bibliothek vor der Verwendung.

Dies gilt natürlich von Fall zu Fall . Beispielsweise nehmen manchmal nur Header-Bibliotheken zuCodegröße & Kompilierungszeiten.

Luchian Grigore
quelle
6
"Eine Bibliothek nur mit Headern zu haben bedeutet auch, dass Sie sich keine Gedanken über verschiedene Plattformen machen müssen, auf denen die Bibliothek verwendet werden könnte": Nur wenn Sie die Bibliothek nicht warten müssen. Ansonsten ist es ein Albtraum mit Fehlerberichten, die Sie mit dem vorhandenen Material nicht reproduzieren oder testen können.
James Kanze
1
Ich habe gerade eine ähnliche Frage zu den Leistungsvorteilen von Headern gestellt. Wie Sie sehen können, gibt es keinen Unterschied in der Codegröße. Die Beispielimplementierung nur für Header lief jedoch 7% langsamer. stackoverflow.com/questions/12290639/…
Homer6
@ Homer6 danke, dass du mich angerufen hast. Ich habe das nie wirklich gemessen.
Luchian Grigore
1
@LuchianGrigore Ich konnte niemanden finden, der auch hatte. Deshalb dauerte es eine Weile, bis ich antwortete. Es gibt so viele spekulative Kommentare zu "Erhöht die Codegröße" und "Speicherverbrauch". Ich habe endlich eine Momentaufnahme der Unterschiede, auch wenn es nur ein Beispiel ist.
Homer6
@ Homer6. Warum würde es die Codegröße nicht erhöhen? Angenommen, Sie erstellen mehrere Bibliotheken, die nur die Header-Bibliothek verwenden, und Ihre App verwendet dann alle Bibliotheken, für die Sie mehrere Kopien benötigen, anstatt eine Verknüpfung mit einer einzelnen gemeinsam genutzten Bibliothek herzustellen.
pooya13
60

Vorteile der Nur-Header-Bibliothek:

  • Vereinfacht den Erstellungsprozess. Sie müssen die Bibliothek nicht erstellen und die kompilierte Bibliothek während des Verknüpfungsschritts des Builds nicht angeben. Wenn Sie über eine kompilierte Bibliothek verfügen, möchten Sie wahrscheinlich mehrere Versionen davon erstellen: Eine mit aktiviertem Debugging, eine mit aktivierter Optimierung und möglicherweise eine weitere ohne Symbole. Und vielleicht noch mehr für ein Multi-Plattform-System.

Nachteile einer Nur-Header-Bibliothek:

  • Größere Objektdateien. Jede Inline-Methode aus der Bibliothek, die in einer Quelldatei verwendet wird, erhält auch ein schwaches Symbol, eine Outline-Definition in der kompilierten Objektdatei für diese Quelldatei. Dies verlangsamt den Compiler und verlangsamt auch den Linker. Der Compiler muss all das Aufblähen erzeugen, und dann muss der Linker es herausfiltern.

  • Längere Zusammenstellung. Zusätzlich zu dem oben erwähnten Aufblähungsproblem dauert die Kompilierung länger, da die Header bei einer Nur-Header-Bibliothek von Natur aus größer sind als bei einer kompilierten Bibliothek. Diese großen Header müssen für jede Quelldatei, die die Bibliothek verwendet, analysiert werden. Ein weiterer Faktor ist, dass diese Header-Dateien in einer Nur-Header-Bibliothek sowohl #includeHeader enthalten müssen, die von den Inline-Definitionen benötigt werden, als auch die Header, die benötigt würden, wenn die Bibliothek als kompilierte Bibliothek erstellt worden wäre.

  • Mehr verworrene Zusammenstellung. Sie erhalten viel mehr Abhängigkeiten mit einer Nur-Header-Bibliothek aufgrund der zusätzlichen #includes, die mit einer Nur-Header-Bibliothek benötigt werden. Wenn Sie die Implementierung einiger Schlüsselfunktionen in der Bibliothek ändern, müssen Sie möglicherweise das gesamte Projekt neu kompilieren. Nehmen Sie diese Änderung in der Quelldatei für eine kompilierte Bibliothek vor. Sie müssen lediglich diese eine Bibliotheksquelldatei neu kompilieren, die kompilierte Bibliothek mit dieser neuen O-Datei aktualisieren und die Anwendung erneut verknüpfen.

  • Für den Menschen schwerer zu lesen. Selbst mit der besten Dokumentation müssen Benutzer einer Bibliothek häufig die Header für die Bibliothek lesen. Die Header in einer Nur-Header-Bibliothek sind mit Implementierungsdetails gefüllt, die das Verständnis der Schnittstelle beeinträchtigen. Bei einer kompilierten Bibliothek sehen Sie nur die Benutzeroberfläche und einen kurzen Kommentar zur Funktionsweise der Implementierung. Das ist normalerweise alles, was Sie möchten. Das ist wirklich alles was du willst. Sie sollten keine Implementierungsdetails kennen müssen, um zu wissen, wie die Bibliothek verwendet wird.

David Hammen
quelle
21
Letzter Punkt macht nicht wirklich Sinn. Jede angemessene Dokumentation enthält die Funktionsdeklaration, Parameter, Rückgabewerte usw. und alle zugehörigen Kommentare. Wenn Sie auf die Header-Datei verweisen müssen, ist die Dokumentation fehlgeschlagen.
Thomas
6
@Thomas - Selbst mit den besten professionellen Bibliotheken muss ich oft auf den "feinen" Header zurückgreifen. In der Tat, wenn die sogenannte "feine" Dokumentation aus dem Code plus Kommentar extrahiert wird, lese ich normalerweise gerne die Überschriften. Der Code und die Kommentare sagen mir mehr als die automatisch generierte Dokumentation.
David Hammen
2
Letzter Punkt ist ungültig. Die Header sind bereits mit Implementierungsdetails in den privaten Mitgliedern gefüllt, sodass die cpp-Datei nicht alle Implementierungsdetails verbirgt. Darüber hinaus sind Sprachen wie C # von Natur aus "nur Header", und die IDE kümmert sich um die Verschleierung von Details ("Zusammenklappen")
Mark Lakata,
2
@Tomas: Stimmen Sie zu, der letzte Punkt ist völlig falsch. Mit Nur-Header-Bibliotheken können Sie Schnittstelle und Implementierung genauso einfach trennen. Sie haben einfach den Schnittstellen-Header #include die Implementierungsdetails. Aus diesem Grund enthalten Boost-Bibliotheken normalerweise ein Unterverzeichnis (und einen Namespace) mit dem Namen detail.
Nemo
4
@ Thomas: Ich bin anderer Meinung. Die Header-Datei ist im Allgemeinen der erste Ort, an den ich zur Dokumentation gehe. Wenn der Header gut geschrieben ist, ist häufig keine externe Dokumentation erforderlich.
Joel Cornett
14

Ich weiß, dass dies ein alter Thread ist, aber niemand hat ABI-Schnittstellen oder bestimmte Compilerprobleme erwähnt. Also dachte ich, ich würde.

Dies basiert im Wesentlichen auf dem Konzept, dass Sie entweder eine Bibliothek mit einem Header schreiben, um sie an andere zu verteilen, oder sich selbst wiederverwenden, anstatt alles in einem Header zu haben. Wenn Sie daran denken, einen Header und Quelldateien wiederzuverwenden und diese in jedem Projekt neu zu kompilieren, trifft dies nicht wirklich zu.

Wenn Sie Ihren C ++ - Code kompilieren und eine Bibliothek mit einem Compiler erstellen, versucht der Benutzer, diese Bibliothek mit einem anderen Compiler oder einer anderen Version desselben Compilers zu verwenden. Aufgrund von Binärinkompatibilität können Linkerfehler oder seltsames Laufzeitverhalten auftreten.

Beispielsweise ändern Compiler-Anbieter häufig ihre Implementierung der STL zwischen den Versionen. Wenn Sie eine Funktion in einer Bibliothek haben, die einen std :: -Vektor akzeptiert, erwartet sie, dass die Bytes in dieser Klasse so angeordnet sind, wie sie beim Kompilieren der Bibliothek angeordnet wurden. Wenn der Anbieter in einer neuen Compilerversion die Effizienz von std :: vector verbessert hat, erkennt der Code des Benutzers die neue Klasse, die möglicherweise eine andere Struktur hat, und übergibt diese neue Struktur an Ihre Bibliothek. Von dort geht es bergab ... Aus diesem Grund wird empfohlen, STL-Objekte nicht über Bibliotheksgrenzen hinweg zu übergeben. Gleiches gilt für CRT-Typen (C Run-Time).

Wenn Sie über die CRT sprechen, müssen Ihre Bibliothek und der Quellcode des Benutzers im Allgemeinen mit derselben CRT verknüpft werden. Wenn Sie in Visual Studio Ihre Bibliothek mit der Multithread-CRT erstellen, der Benutzer jedoch mit der Multithread-Debug-CRT verknüpft, treten Verknüpfungsprobleme auf, da Ihre Bibliothek möglicherweise nicht die benötigten Symbole findet. Ich kann mich nicht erinnern, um welche Funktion es sich handelt, aber für Visual Studio 2015 hat Microsoft eine CRT-Funktion inline erstellt. Plötzlich befand es sich im Header nicht mehr in der CRT-Bibliothek, sodass Bibliotheken, die erwarteten, dass sie zum Zeitpunkt der Verknüpfung gefunden werden, nicht mehr funktionieren konnten, was zu Verbindungsfehlern führte. Das Ergebnis war, dass diese Bibliotheken mit Visual Studio 2015 neu kompiliert werden mussten.

Sie können auch Linkfehler oder seltsames Verhalten erhalten, wenn Sie die Windows-API verwenden, aber mit anderen Unicode-Einstellungen für den Bibliotheksbenutzer erstellen. Dies liegt daran, dass die Windows-API über Funktionen verfügt, die entweder Unicode- oder ASCII-Zeichenfolgen und Makros / Definitionen verwenden, die basierend auf den Unicode-Einstellungen des Projekts automatisch die richtigen Typen verwenden. Wenn Sie eine Zeichenfolge über die Bibliotheksgrenze übergeben, die vom falschen Typ ist, werden die Dinge zur Laufzeit unterbrochen. Oder Sie stellen möglicherweise fest, dass das Programm überhaupt nicht verknüpft ist.

Diese Dinge gelten auch für die Übergabe von Objekten / Typen über Bibliotheksgrenzen von anderen Bibliotheken von Drittanbietern (z. B. einem Eigenvektor oder einer GSL-Matrix). Wenn die Bibliothek eines Drittanbieters ihren Header zwischen dem Kompilieren Ihrer Bibliothek und dem Kompilieren des Codes durch Ihren Benutzer ändert, werden die Dinge kaputt gehen.

Um sicher zu gehen, können Sie Bibliotheksgrenzen nur in Typen und POD (Plain Old Data) erstellen. Idealerweise sollte sich jeder POD in Strukturen befinden, die in Ihren eigenen Headern definiert sind und sich nicht auf Header von Drittanbietern stützen.

Wenn Sie nur eine Header-Bibliothek bereitstellen, wird der gesamte Code mit denselben Compilereinstellungen und mit denselben Headern kompiliert, sodass viele dieser Probleme behoben werden (vorausgesetzt, die Version der dritten teilweise Bibliotheken, die Sie und Ihr Benutzer verwenden, ist API-kompatibel).

Es gibt jedoch oben erwähnte Negative, wie beispielsweise die verlängerte Kompilierungszeit. Möglicherweise führen Sie auch ein Unternehmen, sodass Sie möglicherweise nicht alle Details zur Implementierung Ihres Quellcodes an alle Benutzer weitergeben möchten, falls einer von ihnen diese stiehlt.

Phil Rosenberg
quelle
8

Der Hauptvorteil besteht darin, dass Sie Quellcode bereitstellen müssen, sodass Sie Fehlerberichte auf Computern und Compilern erhalten, von denen Sie noch nie gehört haben. Wenn es sich bei der Bibliothek ausschließlich um Vorlagen handelt, haben Sie nicht viel Auswahl, aber wenn Sie die Wahl haben, ist nur der Header normalerweise eine schlechte technische Wahl. (Auf der anderen Seite bedeutet Header natürlich nur, dass Sie keinen Integrationsvorgang dokumentieren müssen.)

James Kanze
quelle