Aus irgendeinem Grund verfügt unser Unternehmen über eine Kodierungsrichtlinie, in der Folgendes angegeben ist:
Each class shall have it's own header and implementation file.
Wenn wir also schrieb genannt eine Klasse MyString
würden wir ein zugehöriges brauchen MyStringh.h und MyString.cxx .
Macht das noch jemand? Hat jemand irgendwelche Auswirkungen auf die Kompilierungsleistung gesehen? Kompilieren 5000 Klassen in 10000 Dateien genauso schnell wie 5000 Klassen in 2500 Dateien? Wenn nicht, ist der Unterschied spürbar?
[Wir codieren C ++ und verwenden GCC 3.4.4 als unseren täglichen Compiler]
c++
performance
file-organization
Mike Baker
quelle
quelle
Antworten:
Der Begriff hier ist Übersetzungseinheit, und Sie möchten wirklich (wenn möglich) eine Klasse pro Übersetzungseinheit haben, dh eine Klassenimplementierung pro CPP-Datei mit einer entsprechenden .h-Datei mit demselben Namen.
Es ist normalerweise effizienter (vom Kompilieren / Verknüpfen), Dinge auf diese Weise zu tun, insbesondere wenn Sie Dinge wie inkrementelle Verknüpfungen usw. ausführen. Die Idee ist, dass Übersetzungseinheiten so isoliert sind, dass Sie beim Ändern einer Übersetzungseinheit nicht viele Dinge neu erstellen müssen, wie Sie es tun müssten, wenn Sie anfangen würden, viele Abstraktionen in einer einzigen Übersetzungseinheit zusammenzufassen.
Außerdem werden Sie feststellen, dass viele Fehler / Diagnosen über den Dateinamen ("Fehler in Myclass.cpp, Zeile 22") gemeldet werden. Dies ist hilfreich, wenn zwischen Dateien und Klassen eine Eins-zu-Eins-Entsprechung besteht. (Oder ich nehme an, Sie könnten es eine 2 zu 1 Korrespondenz nennen).
quelle
Überwältigt von Tausenden von Codezeilen?
Ein Satz von Header- / Quelldateien pro Klasse in einem Verzeichnis zu haben, kann übertrieben erscheinen. Und wenn die Anzahl der Klassen auf 100 oder 1000 steigt, kann es sogar beängstigend sein.
Aber nachdem wir mit Quellen gespielt haben, die der Philosophie "Lasst uns alles zusammenstellen" folgen, ist die Schlussfolgerung, dass nur derjenige, der die Datei geschrieben hat, die Hoffnung hat, nicht im Inneren verloren zu gehen. Selbst mit einer IDE ist es leicht, Dinge zu übersehen, denn wenn Sie mit einer Quelle von 20.000 Zeilen spielen, schließen Sie einfach Ihren Verstand für alles, was sich nicht genau auf Ihr Problem bezieht.
Beispiel aus dem wirklichen Leben: Die in diesen Quellen mit tausend Zeilen definierte Klassenhierarchie schloss sich zu einer Diamantvererbung zusammen, und einige Methoden wurden in untergeordneten Klassen von Methoden mit genau demselben Code überschrieben. Dies wurde leicht übersehen (wer möchte einen Quellcode mit 20.000 Zeilen untersuchen / überprüfen?), Und als die ursprüngliche Methode geändert wurde (Fehlerkorrektur), war der Effekt nicht so universell wie ausgenommen.
Abhängigkeiten werden kreisförmig?
Ich hatte dieses Problem mit Vorlagencode, aber ich sah ähnliche Probleme mit normalem C ++ - und C-Code.
Wenn Sie Ihre Quellen in 1 Header pro Struktur / Klasse aufteilen, können Sie:
Im quellengesteuerten Code können Klassenabhängigkeiten dazu führen, dass Klassen regelmäßig in der Datei nach oben und unten verschoben werden, damit der Header kompiliert wird. Sie möchten die Entwicklung solcher Bewegungen nicht untersuchen, wenn Sie dieselbe Datei in verschiedenen Versionen vergleichen.
Durch separate Header wird der Code modularer, schneller zu kompilieren und es ist einfacher, seine Entwicklung anhand verschiedener Versionsunterschiede zu untersuchen
Für mein Vorlagenprogramm musste ich meine Header in zwei Dateien aufteilen: Die HPP-Datei mit der Deklaration / Definition der Vorlagenklasse und die INL-Datei mit den Definitionen der genannten Klassenmethoden.
Wenn Sie den gesamten Code in einen einzigen Header einfügen, müssen Sie die Klassendefinitionen am Anfang dieser Datei und die Methodendefinitionen am Ende einfügen.
Und wenn jemand nur einen kleinen Teil des Codes mit der Lösung nur für einen Header benötigt, muss er immer noch für die langsamere Kompilierung bezahlen.
(§) Beachten Sie, dass Sie zirkuläre Abhängigkeiten zwischen Klassen haben können, wenn Sie wissen, welche Klasse welche besitzt. Dies ist eine Diskussion über Klassen, die Kenntnisse über die Existenz anderer Klassen haben, nicht über Antipattern für zirkuläre Abhängigkeiten von shared_ptr.
Ein letztes Wort: Überschriften sollten autark sein
Eines muss jedoch von einer Lösung aus mehreren Headern und mehreren Quellen beachtet werden.
Wenn Sie einen Header einfügen, egal welcher Header, muss Ihre Quelle sauber kompiliert werden.
Jeder Header sollte autark sein. Sie sollten Code entwickeln und nicht nach Schätzen suchen, indem Sie Ihr Projekt mit mehr als 10.000 Quelldateien durchsuchen, um herauszufinden, welcher Header das Symbol im Header mit 1.000 Zeilen definiert, den Sie nur aufgrund einer Aufzählung einfügen müssen.
Dies bedeutet, dass entweder jeder Header alle verwendeten Symbole definiert oder deklariert oder alle erforderlichen Header (und nur die erforderlichen Header) enthält.
Frage zu zirkulären Abhängigkeiten
Unterstrich-d fragt:
Angenommen, Sie haben zwei Klassenvorlagen, A und B.
Angenommen, die Definition der Klasse A (bzw. B) hat einen Zeiger auf B (bzw. A). Nehmen wir auch an, die Methoden der Klasse A (bzw. B) rufen tatsächlich Methoden von B (bzw. A) auf.
Sie haben eine zirkuläre Abhängigkeit sowohl bei der Definition der Klassen als auch bei der Implementierung ihrer Methoden.
Wenn A und B normale Klassen wären und die Methoden von A und B in CPP-Dateien enthalten wären, gäbe es kein Problem: Sie würden eine Vorwärtsdeklaration verwenden, einen Header für jede Klassendefinition haben, dann würde jeder CPP beide HPP enthalten.
Da Sie jedoch Vorlagen haben, müssen Sie diese Muster tatsächlich oben reproduzieren, jedoch nur mit Überschriften.
Das heisst:
Jeder Header weist die folgenden Merkmale auf:
Der naive Benutzer wird A.hpp und / oder B.hpp einschließen, wodurch das ganze Durcheinander ignoriert wird.
Und mit dieser Organisation kann der Bibliotheksschreiber die zirkulären Abhängigkeiten zwischen A und B lösen, während beide Klassen in separaten Dateien gespeichert werden. Sobald Sie das Schema verstanden haben, können Sie leicht navigieren.
Bitte beachten Sie, dass es sich um einen Randfall handelte (zwei Vorlagen kennen sich). Ich erwarte, dass der meiste Code diesen Trick nicht benötigt.
quelle
.hpp
und.tpp
Dateien löste ... aber mein Gedächtnis reichte nicht aus, um die Zuordnung herzustellen - oder vielleicht Ich habe es ausgeblendet. : D Zum Glück habe ich diesen Bereich seitdem so umgestaltet, dass er weit weniger verwirrend ist, da es, wie Sie angedeutet haben, wahrscheinlich ein Muster ist, das in der Praxis selten benötigt wird.Wir machen das bei der Arbeit, es ist einfach einfacher, Dinge zu finden, wenn die Klasse und die Dateien den gleichen Namen haben. Was die Leistung betrifft, sollten Sie wirklich nicht 5000 Klassen in einem einzelnen Projekt haben. In diesem Fall ist möglicherweise ein Refactoring angebracht.
Es gibt jedoch Fälle, in denen mehrere Klassen in einer Datei vorhanden sind. Und dann ist es nur eine private Hilfsklasse für die Hauptklasse der Datei.
quelle
+1 zur Trennung. Ich bin gerade auf ein Projekt gestoßen, bei dem sich einige Klassen in Dateien mit einem anderen Namen befinden oder mit einer anderen Klasse zusammengefasst sind, und es ist unmöglich, diese schnell und effizient zu finden. Sie können mehr Ressourcen auf einen Build werfen - Sie können verlorene Programmiererzeit nicht wettmachen, da er nicht die richtige Datei zum Bearbeiten findet.
quelle
Die Trennung von Klassen in separate Dateien ist nicht nur einfach "klarer", sondern erleichtert es auch mehreren Entwicklern, sich nicht gegenseitig auf die Zehen zu treten. Es wird weniger Zusammenführungen geben, wenn Änderungen an Ihrem Versionskontroll-Tool vorgenommen werden müssen.
quelle
grep
oder was auch immer. Zum Beispiel ist es viel einfacher zu tippengit log SmallFile.cpp
alsgit log GiganticFile.cpp | grep -b4 -a4 SureHopeIPutTheMagicWordInEveryCommitMsg
(ein erfundenes Beispiel, aber Sie bekommen die Idee).Die meisten Orte, an denen ich gearbeitet habe, haben diese Praxis befolgt. Ich habe tatsächlich Codierungsstandards für BAE (Aust.) Zusammen mit den Gründen geschrieben, warum anstatt nur etwas ohne wirkliche Rechtfertigung in Stein zu schnitzen.
In Bezug auf Ihre Frage zu Quelldateien ist es nicht so viel Zeit zum Kompilieren, sondern vielmehr ein Problem, das relevante Code-Snippet überhaupt zu finden. Nicht jeder verwendet eine IDE. Und zu wissen, dass Sie nur nach MyClass.h und MyClass.cpp suchen, spart wirklich Zeit im Vergleich dazu, "grep MyClass *. (H | cpp)" über eine Reihe von Dateien auszuführen und dann die # include MyClass.h-Anweisungen herauszufiltern ...
Wohlgemerkt, es gibt Workarounds für die Auswirkung einer großen Anzahl von Quelldateien auf die Kompilierungszeiten. Eine interessante Diskussion finden Sie unter Large Scale C ++ Software Design von John Lakos.
Vielleicht möchten Sie auch Code Complete von Steve McConnell lesen, um ein hervorragendes Kapitel über Codierungsrichtlinien zu erhalten. Tatsächlich ist dieses Buch eine großartige Lektüre, auf die ich regelmäßig zurückkomme
quelle
Dies ist gängige Praxis, insbesondere um .h in die Dateien aufnehmen zu können, die es benötigen. Natürlich ist die Leistung beeinträchtigt, aber versuchen Sie nicht, über dieses Problem nachzudenken, bis es auftritt :).
Beginnen Sie besser mit den getrennten Dateien und versuchen Sie anschließend, die üblicherweise verwendeten .hs zusammenzuführen, um die Leistung zu verbessern, wenn dies wirklich erforderlich ist. Es kommt alles auf Abhängigkeiten zwischen Dateien an und dies ist für jedes Projekt sehr spezifisch.
quelle
Wie andere bereits gesagt haben, besteht die beste Vorgehensweise darin, jede Klasse aus Sicht der Codepflege und der Verständlichkeit in einer eigenen Übersetzungseinheit zu platzieren. Bei großen Systemen ist dies jedoch manchmal nicht ratsam. Weitere Informationen zu den Kompromissen finden Sie im Abschnitt "Vergrößern dieser Quelldateien" in diesem Artikel von Bruce Dawson.
quelle
Ich fand diese Richtlinien besonders nützlich, wenn es um Header-Dateien geht: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Header_Files
quelle
Es ist sehr hilfreich, nur eine Klasse pro Datei zu haben. Wenn Sie jedoch über Bulkbuild-Dateien erstellen, die alle einzelnen C ++ - Dateien enthalten, werden die Kompilierungen beschleunigt, da die Startzeit für viele Compiler relativ lang ist.
quelle
Ich bin überrascht, dass fast jeder dafür ist, eine Datei pro Klasse zu haben. Das Problem dabei ist, dass es im Zeitalter des 'Refactorings' schwierig sein kann, die Datei- und Klassennamen synchron zu halten. Jedes Mal, wenn Sie einen Klassennamen ändern, müssen Sie auch den Dateinamen ändern. Dies bedeutet, dass Sie auch überall dort Änderungen vornehmen müssen, wo die Datei enthalten ist.
Ich persönlich gruppiere verwandte Klassen in einer einzigen Datei und gebe einer solchen Datei dann einen aussagekräftigen Namen, der sich nicht ändern muss, selbst wenn sich ein Klassenname ändert. Wenn Sie weniger Dateien haben, können Sie auch einfacher durch einen Dateibaum scrollen. Ich verwende Visual Studio unter Windows und Eclipse CDT unter Linux und beide haben Tastenkombinationen, mit denen Sie direkt zu einer Klassendeklaration gelangen, sodass das Auffinden einer Klassendeklaration einfach und schnell ist.
Trotzdem denke ich, dass es sinnvoll sein kann, eine Klasse pro Datei zu haben, wenn ein Projekt abgeschlossen ist oder seine Struktur sich „verfestigt“ hat und Namensänderungen selten werden. Ich wünschte, es gäbe ein Tool, mit dem Klassen extrahiert und in unterschiedlichen .h- und .cpp-Dateien abgelegt werden könnten. Aber ich sehe das nicht als wesentlich an.
Die Wahl hängt auch von der Art des Projekts ab, an dem gearbeitet wird. Meiner Meinung nach verdient das Problem keine Schwarz-Weiß-Antwort, da jede Wahl Vor- und Nachteile hat.
quelle
sed -i 's/\<TheClassName\>/TheNewName/g' In*.?pp
=>rename 's/\<TheClassName\>/TheNewName/' In*.?pp
. Bei Bedarf in einigen Unterverzeichnissen wiederholen. Und wenn dassed
ohne Konflikte funktioniert hat (oder Sie einen genaueren geschrieben haben), dannrename
ist das viel wahrscheinlicher. Ich habe fast keine Erfahrung mit GUI-IDEs, aber ich hoffe, dass sie dies noch einfacher machen, sodass ich kein Problem sehe.Die gleiche Regel gilt hier, es werden jedoch einige Ausnahmen angegeben, wo dies zulässig ist.
quelle
Zwei Wörter: Ockhams Rasiermesser. Behalten Sie eine Klasse pro Datei mit dem entsprechenden Header in einer separaten Datei. Wenn Sie etwas anderes tun, z. B. ein Stück Funktionalität pro Datei behalten, müssen Sie alle Arten von Regeln darüber erstellen, was ein Teil der Funktionalität ausmacht. Es gibt viel mehr zu gewinnen, wenn Sie eine Klasse pro Datei behalten. Und selbst halbwegs anständige Tools können große Mengen von Dateien verarbeiten. Halte es einfach, mein Freund.
quelle