Ü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:
- Beschleunigen Sie die Kompilierung, da Sie die Symbol-Vorwärtsdeklaration verwenden können, anstatt ganze Objekte einzuschließen
- Zirkuläre Abhängigkeiten zwischen Klassen haben (§) (dh Klasse A hat einen Zeiger auf B und B hat einen Zeiger auf A)
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:
Können Sie erklären, wie sich die Verwendung separater Header auf zirkuläre Abhängigkeiten auswirkt? Ich glaube nicht. Wir können trivialerweise eine zirkuläre Abhängigkeit erstellen, selbst wenn beide Klassen vollständig im selben Header deklariert sind, indem wir einfach eine im Voraus vorwärts deklarieren, bevor wir im anderen ein Handle dafür deklarieren. Alles andere scheint großartige Punkte zu sein, aber die Idee, dass separate Header zirkuläre Abhängigkeiten ermöglichen, scheint weit entfernt zu sein
underscore_d, 13. November um 23:20 Uhr
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:
- ein Definitionsheader A.def.hpp und B.def.hpp
- einen Implementierungsheader A.inl.hpp und B.inl.hpp
- der Einfachheit halber ein "naiver" Header A.hpp und B.hpp
Jeder Header weist die folgenden Merkmale auf:
- In A.def.hpp (bzw. B.def.hpp) haben Sie eine Vorwärtsdeklaration der Klasse B (bzw. A), mit der Sie einen Zeiger / Verweis auf diese Klasse deklarieren können
- A.inl.hpp (bzw. B.inl.hpp) umfasst sowohl A.def.hpp als auch B.def.hpp, wodurch Methoden von A (bzw. B) die Klasse B (bzw. A) verwenden können. .
- A.hpp (bzw. B.hpp) umfasst direkt sowohl A.def.hpp als auch A.inl.hpp (bzw. B.def.hpp und B.inl.hpp).
- Natürlich müssen alle Header autark und durch Header Guards geschützt sein
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.