Mein persönlicher Stil mit C ++ besteht immer darin, Klassendeklarationen in eine Include-Datei und Definitionen in eine CPP-Datei einzufügen, ähnlich wie in Lokis Antwort auf C ++ - Header-Dateien, Codetrennung . Zugegeben, ein Teil des Grundes, warum ich diesen Stil mag, hat wahrscheinlich mit all den Jahren zu tun, die ich mit dem Codieren von Modula-2 und Ada verbracht habe. Beide haben ein ähnliches Schema mit Spezifikationsdateien und Body-Dateien.
Ich habe einen Kollegen, der sich in C ++ viel besser auskennt als ich, und der darauf besteht, dass alle C ++ - Deklarationen, wo möglich, die Definitionen genau dort in die Header-Datei aufnehmen. Er sagt nicht, dass dies ein gültiger alternativer Stil oder sogar ein etwas besserer Stil ist, sondern dass dies der neue allgemein akzeptierte Stil ist, den jetzt jeder für C ++ verwendet.
Ich bin nicht mehr so geschmeidig wie früher, also bin ich nicht wirklich darauf bedacht, auf seinen Zug zu klettern, bis ich dort oben noch ein paar Leute mit ihm sehe. Wie häufig ist diese Redewendung wirklich?
Nur um den Antworten eine gewisse Struktur zu geben: Ist es jetzt The Way , sehr häufig, etwas häufig, ungewöhnlich oder verrückt nach Bug-Outs?
Antworten:
Ihr Mitarbeiter ist falsch, der übliche Weg war und ist es, Code in CPP-Dateien (oder eine beliebige Erweiterung) und Deklarationen in Headern einzufügen.
Es ist gelegentlich sinnvoll, Code in den Header einzufügen. Dies kann ein geschickteres Inlining durch den Compiler ermöglichen. Gleichzeitig kann dies Ihre Kompilierungszeiten zerstören, da der gesamte Code jedes Mal verarbeitet werden muss, wenn er vom Compiler aufgenommen wird.
Schließlich ist es oft ärgerlich, kreisförmige Objektbeziehungen zu haben (manchmal erwünscht), wenn der gesamte Code die Überschriften sind.
Fazit: Sie hatten Recht, er liegt falsch.
EDIT: Ich habe über Ihre Frage nachgedacht. Es gibt einen Fall, in dem das, was er sagt, wahr ist. Vorlagen. Viele neuere "moderne" Bibliotheken wie Boost verwenden häufig Vorlagen und sind häufig "nur Header". Dies sollte jedoch nur beim Umgang mit Vorlagen erfolgen, da dies die einzige Möglichkeit ist, dies beim Umgang mit Vorlagen zu tun.
EDIT: Einige Leute möchten etwas mehr Klarheit, hier sind einige Gedanken zu den Nachteilen beim Schreiben von "nur Header" -Code:
Wenn Sie sich umschauen, werden Sie eine Menge Leute sehen, die versuchen, einen Weg zu finden, um die Kompilierungszeiten beim Umgang mit Boost zu verkürzen. Beispiel: So reduzieren Sie die Kompilierungszeiten mit Boost Asio , bei dem eine 14-Sekunden-Kompilierung einer einzelnen 1K-Datei mit Boost angezeigt wird. 14s scheinen nicht "explodieren" zu sein, aber es ist sicherlich viel länger als typisch und kann sich ziemlich schnell summieren. Bei einem großen Projekt. Nur-Header-Bibliotheken wirken sich auf messbare Weise auf die Kompilierungszeiten aus. Wir tolerieren es einfach, weil Boost so nützlich ist.
Darüber hinaus gibt es viele Dinge, die nicht nur in Headern ausgeführt werden können (selbst Boost enthält Bibliotheken, mit denen Sie für bestimmte Teile wie Threads, Dateisysteme usw. verknüpfen müssen). Ein primäres Beispiel ist, dass Sie keine einfachen globalen Objekte in nur Header-Bibliotheken haben können (es sei denn, Sie greifen auf den Gräuel zurück, der ein Singleton ist), da Sie auf mehrere Definitionsfehler stoßen. HINWEIS: Mit den Inline-Variablen von C ++ 17 kann dieses Beispiel in Zukunft ausgeführt werden.
Als letzter Punkt wird bei Verwendung von Boost als Beispiel für Nur-Header-Code häufig ein großes Detail übersehen.
Boost ist eine Bibliothek, kein Code auf Benutzerebene. so oft ändert es sich nicht. Wenn Sie im Benutzercode alles in Kopfzeilen einfügen, müssen Sie bei jeder kleinen Änderung das gesamte Projekt neu kompilieren. Das ist eine enorme Zeitverschwendung (und gilt nicht für Bibliotheken, die sich nicht von Kompilieren zu Kompilieren ändern). Wenn Sie Dinge zwischen Header / Quelle und noch besser aufteilen und Forward-Deklarationen verwenden, um Includes zu reduzieren, können Sie Stunden der Neukompilierung sparen, wenn Sie diese über einen Tag addieren.
quelle
An dem Tag, an dem sich C ++ - Programmierer auf The Way einigen , werden sich Lämmer mit Löwen hinlegen, Palästinenser werden Israelis umarmen und Katzen und Hunde dürfen heiraten.
Die Trennung zwischen .h- und .cpp-Dateien ist zu diesem Zeitpunkt meist willkürlich, ein Überbleibsel von Compiler-Optimierungen, die längst vorbei sind. Aus meiner Sicht gehören Deklarationen in den Header und Definitionen in die Implementierungsdatei. Aber das ist nur Gewohnheit, keine Religion.
quelle
Code in Headern ist im Allgemeinen eine schlechte Idee, da er die Neukompilierung aller Dateien erzwingt, die den Header enthalten, wenn Sie den tatsächlichen Code anstelle der Deklarationen ändern. Dies verlangsamt auch die Kompilierung, da Sie den Code in jeder Datei analysieren müssen, die den Header enthält.
Ein Grund für Code in Header-Dateien besteht darin, dass das Schlüsselwort inline im Allgemeinen ordnungsgemäß funktioniert und Vorlagen verwendet werden, die in anderen CPP-Dateien instanziiert werden.
quelle
Was Sie möglicherweise als Mitarbeiter informiert, ist die Vorstellung, dass der meiste C ++ - Code als Vorlage verwendet werden sollte, um maximale Benutzerfreundlichkeit zu gewährleisten. Und wenn es als Vorlage dient, muss sich alles in einer Header-Datei befinden, damit der Client-Code es sehen und instanziieren kann. Wenn es gut genug für Boost und die STL ist, ist es gut genug für uns.
Ich stimme diesem Standpunkt nicht zu, aber es kann sein, woher er kommt.
quelle
Ich denke, Ihr Kollege ist schlau und Sie haben auch Recht.
Die nützlichen Dinge, die ich fand, wenn ich alles in die Überschriften stecke, sind:
Keine Notwendigkeit, Header und Quellen zu schreiben und zu synchronisieren.
Die Struktur ist einfach und keine kreisförmigen Abhängigkeiten zwingen den Codierer, eine "bessere" Struktur zu erstellen.
Tragbar, einfach in ein neues Projekt einzubetten.
Ich bin mit dem Problem der Kompilierungszeit einverstanden, aber ich denke, wir sollten das beachten:
Durch die Änderung der Quelldatei werden sehr wahrscheinlich die Header-Dateien geändert, was dazu führt, dass das gesamte Projekt erneut kompiliert wird.
Die Kompilierungsgeschwindigkeit ist viel schneller als zuvor. Und wenn Sie ein Projekt haben, das mit langer Zeit und hoher Frequenz erstellt werden soll, kann dies darauf hinweisen, dass Ihr Projektdesign Fehler aufweist. Durch die Aufteilung der Aufgaben in verschiedene Projekte und Module kann dieses Problem vermieden werden.
Zuletzt möchte ich nur Ihren Kollegen unterstützen, nur aus meiner persönlichen Sicht.
quelle
Oft füge ich triviale Elementfunktionen in die Header-Datei ein, damit sie inline eingefügt werden können. Aber um den gesamten Code dort abzulegen, nur um mit Vorlagen konsistent zu sein? Das sind einfache Nüsse.
Denken Sie daran: Eine dumme Konsequenz ist der Hobgoblin der kleinen Köpfe .
quelle
A<B>
in einer CPP-Datei bereitstellt und der Benutzer dann eine wünschtA<C>
?Wie Tuomas sagte, sollte Ihr Header minimal sein. Um vollständig zu sein, werde ich etwas erweitern.
Ich persönlich verwende 4 Arten von Dateien in meinen
C++
Projekten:Außerdem verbinde ich dies mit einer anderen Regel: Definieren Sie nicht, was Sie weiterleiten können. Obwohl ich dort natürlich vernünftig bin (Pimpl überall zu verwenden ist ein ziemlicher Aufwand).
Dies bedeutet, dass ich eine Vorwärtserklärung gegenüber einer
#include
Direktive in meinen Kopfzeilen bevorzuge, wenn ich mit ihnen durchkommen kann.Schließlich verwende ich auch eine Sichtbarkeitsregel: Ich beschränke die Bereiche meiner Symbole so weit wie möglich, damit sie die äußeren Bereiche nicht verschmutzen.
Alles in allem:
Der Lebensretter hier ist, dass der Forward-Header meistens unbrauchbar ist: nur für den Fall von
typedef
odertemplate
und ebenso für den Implementierungs-Header erforderlich ;)quelle
Um mehr Spaß zu haben, können Sie
.ipp
Dateien hinzufügen , die die Vorlagenimplementierung enthalten (die in enthalten ist.hpp
), während sie.hpp
die Benutzeroberfläche enthalten.Abgesehen von Vorlagencode (je nach Projekt kann dies die Mehrheit oder Minderheit der Dateien sein) gibt es normalen Code, und hier ist es besser, die Deklarationen und Definitionen zu trennen. Stellen Sie bei Bedarf auch Vorwärtsdeklarationen bereit - dies kann sich auf die Kompilierungszeit auswirken.
quelle
Wenn ich eine neue Klasse schreibe, füge ich im Allgemeinen den gesamten Code in die Klasse ein, damit ich nicht in einer anderen Datei danach suchen muss. Nachdem alles funktioniert hat, zerlege ich den Hauptteil der Methoden in die cpp-Datei Lassen Sie die Prototypen in der HPP-Datei.
quelle
Wenn dieser neue Weg wirklich der Weg ist , sind wir in unseren Projekten möglicherweise in eine andere Richtung gelaufen.
Weil wir versuchen, alle unnötigen Dinge in Headern zu vermeiden. Dazu gehört die Vermeidung von Header-Kaskaden. Code in Headern benötigt wahrscheinlich einen anderen Header, der einen anderen Header usw. benötigt. Wenn wir gezwungen sind, Vorlagen zu verwenden, versuchen wir zu vermeiden, dass Header zu viel mit Vorlagenmaterial übersät werden.
Wir verwenden auch das "undurchsichtige Zeiger" -Muster .
Mit diesen Methoden können wir schnellere Builds erstellen als die meisten unserer Kollegen. Und ja ... das Ändern von Code oder Klassenmitgliedern führt nicht zu großen Neuerstellungen.
quelle
Ich persönlich mache das in meinen Header-Dateien:
Ich mag es nicht, den Code für die Methoden mit der Klasse zu mischen, da es mir schwer fällt, schnell nachzuschlagen.
Ich würde nicht ALLE Methoden in die Header-Datei einfügen. Der Compiler kann (normalerweise) keine virtuellen Methoden einbinden und wird (wahrscheinlich) nur kleine Methoden ohne Schleifen einbinden (hängt vollständig vom Compiler ab).
Das Ausführen der Methoden in der Klasse ist gültig ... aber aus lesbarer Sicht mag ich es nicht. Wenn Sie die Methoden in den Header einfügen, werden sie nach Möglichkeit inline.
quelle
IMHO, er hat NUR Verdienst, wenn er Vorlagen und / oder Metaprogrammierung macht. Es gibt bereits viele Gründe, warum Sie Header-Dateien auf Deklarationen beschränken. Sie sind nur das ... Überschriften. Wenn Sie Code einfügen möchten, kompilieren Sie ihn als Bibliothek und verknüpfen ihn.
quelle
Ich habe die gesamte Implementierung aus der Klassendefinition entfernt. Ich möchte die Sauerstoffkommentare aus der Klassendefinition haben.
quelle
Ich finde es absolut absurd, ALLE Ihre Funktionsdefinitionen in die Header-Datei aufzunehmen. Warum? Weil die Header-Datei als PUBLIC-Schnittstelle zu Ihrer Klasse verwendet wird. Es ist die Außenseite der "Black Box".
Wenn Sie sich eine Klasse ansehen müssen, um zu verweisen, wie sie verwendet wird, sollten Sie sich die Header-Datei ansehen. Die Header-Datei sollte eine Liste der möglichen Funktionen enthalten (kommentiert, um die Details zur Verwendung der einzelnen Funktionen zu beschreiben) und eine Liste der Mitgliedsvariablen enthalten. Es sollte NICHT enthalten sein, wie jede einzelne Funktion implementiert wird, da dies eine Bootsladung unnötiger Informationen ist und nur die Header-Datei überfüllt.
quelle
Hängt das nicht wirklich von der Komplexität des Systems und den internen Konventionen ab?
Im Moment arbeite ich an einem neuronalen Netzwerksimulator, der unglaublich komplex ist, und der akzeptierte Stil, den ich voraussichtlich verwenden werde, ist:
Klassendefinitionen
in classname.h Klassencode in classnameCode.h
ausführbarer Code in classname.cpp
Dies teilt die vom Benutzer erstellten Simulationen von den vom Entwickler erstellten Basisklassen auf und funktioniert in der jeweiligen Situation am besten.
Es würde mich jedoch überraschen, wenn Leute dies beispielsweise in einer Grafikanwendung oder einer anderen Anwendung tun, deren Zweck nicht darin besteht, Benutzern eine Codebasis zur Verfügung zu stellen.
quelle
Vorlagencode sollte nur in Kopfzeilen enthalten sein. Abgesehen davon sollten alle Definitionen außer Inlines in .cpp sein. Das beste Argument dafür wären die Standardbibliotheksimplementierungen, die der gleichen Regel folgen. Sie würden nicht widersprechen, dass die Entwickler von std lib diesbezüglich Recht haben würden.
quelle
libstdc++
scheint (AFAICS) setzt fast nichtssrc
und fast alles , was ininclude
, ob es ‚Muss‘ in einem Header sein. Ich denke also nicht, dass dies ein genaues / nützliches Zitat ist. Wie auch immer, ich denke nicht, dass stdlibs ein Modell für Benutzercode sind: Sie werden offensichtlich von hochqualifizierten Codierern geschrieben, aber um verwendet zu werden , nicht gelesen: Sie abstrahieren hohe Komplexität, über die die meisten Codierer nicht nachdenken sollten , müssen_Reserved
__names
überall hässlich sein , um Konflikte mit dem Benutzer zu vermeiden, Kommentare und Abstände liegen unter dem, was ich empfehlen würde usw. Sie sind auf enge Weise beispielhaft.Ich denke, Ihr Mitarbeiter hat Recht, solange er nicht in den Prozess zum Schreiben von ausführbarem Code in den Header eintritt. Ich denke, das richtige Gleichgewicht besteht darin, dem von GNAT Ada angegebenen Pfad zu folgen, in dem die ADS-Datei eine absolut angemessene Schnittstellendefinition des Pakets für seine Benutzer und für seine Kinder enthält.
Übrigens, Ted, haben Sie sich in diesem Forum die aktuelle Frage zur Ada-Bindung an die CLIPS-Bibliothek angesehen, die Sie vor einigen Jahren geschrieben haben und die nicht mehr verfügbar ist (relevante Webseiten sind jetzt geschlossen). Selbst wenn diese Bindung zu einer alten Clips-Version erstellt wurde, könnte sie ein gutes Startbeispiel für jemanden sein, der bereit ist, die CLIPS-Inferenz-Engine in einem Ada 2012-Programm zu verwenden.
quelle