Wenn Sie eine C ++ - Klasse deklarieren, empfiehlt es sich normalerweise, nur die Deklaration in die Header-Datei und die Implementierung in eine Quelldatei einzufügen. Es scheint jedoch, dass dieses Entwurfsmodell für Vorlagenklassen nicht funktioniert.
Wenn Sie online suchen, scheint es zwei Meinungen darüber zu geben, wie Vorlagenklassen am besten verwaltet werden können:
1. Gesamte Deklaration und Implementierung im Header.
Dies ist ziemlich einfach, führt aber meiner Meinung nach dazu, dass es schwierig ist, Codedateien zu pflegen und zu bearbeiten, wenn die Vorlage groß wird.
2. Schreiben Sie die Implementierung in eine am Ende enthaltene Template-Include-Datei (.tpp).
Dies scheint mir eine bessere Lösung zu sein, scheint aber nicht weit verbreitet zu sein. Gibt es einen Grund, warum dieser Ansatz minderwertig ist?
Ich weiß, dass der Codestil oft von persönlichen Vorlieben oder Legacy-Stilen bestimmt wird. Ich starte ein neues Projekt (Portierung eines alten C-Projekts nach C ++) und bin relativ neu im OO-Design und möchte von Anfang an Best Practices befolgen.
quelle
Antworten:
Wenn Sie eine C ++ - Klasse mit Vorlagen schreiben, haben Sie normalerweise drei Möglichkeiten:
(1) Deklaration und Definition in die Kopfzeile einfügen.
oder
Profi:
Con:
Foo
als Mitglied deklarieren , müssen Sie diese einschließenfoo.h
. Dies bedeutet, dass das Ändern der Implementierung vonFoo::f
Propagates sowohl über Header- als auch über Quelldateien erfolgt.Schauen wir uns die Auswirkungen der Neuerstellung genauer an: Bei C ++ - Klassen ohne Vorlage fügen Sie Deklarationen in .h und Methodendefinitionen in .cpp ein. Auf diese Weise muss beim Ändern der Implementierung einer Methode nur eine CPP neu kompiliert werden. Dies ist für Vorlagenklassen anders, wenn die .h Ihren gesamten Code enthält. Schauen Sie sich das folgende Beispiel an:
Hier ist die einzige Verwendung von
Foo::f
drinnenbar.cpp
. Wenn Sie die Umsetzung jedoch ändernFoo::f
, beidebar.cpp
undqux.cpp
Bedarf neu kompiliert werden. Die Implementierung vonFoo::f
Leben in beiden Dateien, obwohl kein Teil vonQux
direkt etwas von verwendetFoo::f
. Bei großen Projekten kann dies bald zum Problem werden.(2) Fügen Sie die Deklaration in .h und die Definition in .tpp ein und fügen Sie sie in .h ein.
Profi:
Con:
Diese Lösung trennt Deklaration und Methodendefinition in zwei separaten Dateien, genau wie .h / .cpp. Dieser Ansatz hat jedoch das gleiche Wiederherstellungsproblem wie (1) , da der Header direkt die Methodendefinitionen enthält.
(3) Fügen Sie die Deklaration in .h und die Definition in .tpp ein, schließen Sie jedoch .tpp nicht in .h ein.
Profi:
Con:
Foo
einer Klasse ein Mitglied hinzufügenBar
, müssen Sie esfoo.h
in die Kopfzeile aufnehmen. Wenn SieFoo::f
eine .cpp aufrufen, müssen Sie diese auch einschließenfoo.tpp
.Dieser Ansatz reduziert die Auswirkungen auf die Neuerstellung, da nur CPP-Dateien, die wirklich verwendet
Foo::f
werden, neu kompiliert werden müssen. Dies hat jedoch einen Preis: Alle diese Dateien müssen enthalten seinfoo.tpp
. Nehmen Sie das Beispiel von oben und verwenden Sie den neuen Ansatz:Wie Sie sehen können, ist der einzige Unterschied das zusätzliche Einschließen von
foo.tpp
inbar.cpp
. Dies ist unpraktisch und das Hinzufügen eines zweiten Includes für eine Klasse, je nachdem, ob Sie Methoden aufrufen, erscheint sehr hässlich. Sie reduzieren jedoch die Auswirkungenbar.cpp
auf die Neuerstellung : Muss nur neu kompiliert werden, wenn Sie die Implementierung von ändernFoo::f
. Die Dateiqux.cpp
muss nicht neu kompiliert werden.Zusammenfassung:
Wenn Sie eine Bibliothek implementieren, müssen Sie sich normalerweise nicht um die Auswirkungen der Wiederherstellung kümmern. Benutzer Ihrer Bibliothek greifen auf eine Version zu und verwenden sie. Die Implementierung der Bibliothek ändert sich in der täglichen Arbeit des Benutzers nicht. In solchen Fällen kann die Bibliothek den Ansatz (1) oder (2) verwenden, und es ist nur eine Frage des Geschmacks, welchen Sie wählen.
Wenn Sie jedoch an einer Anwendung oder an einer internen Bibliothek Ihres Unternehmens arbeiten, ändert sich der Code häufig. Sie müssen sich also um die Wiederherstellung der Auswirkungen kümmern. Die Wahl von Ansatz (3) kann eine gute Option sein, wenn Sie Ihre Entwickler dazu bringen, das zusätzliche Include zu akzeptieren.
quelle
Ähnlich wie bei der
.tpp
Idee (die ich noch nie gesehen habe) haben wir die meisten Inline-Funktionen in eine-inl.hpp
Datei eingefügt, die am Ende der üblichen.hpp
Datei enthalten ist.Wie andere angeben, bleibt die Benutzeroberfläche dadurch lesbar, indem die Unordnung der Inline-Implementierungen (wie Vorlagen) in eine andere Datei verschoben wird. Wir erlauben einige Schnittstellen-Inlines, versuchen jedoch, sie auf kleine, normalerweise einzeilige Funktionen zu beschränken.
quelle
Eine Pro-Münze der 2. Variante ist, dass Ihre Header aufgeräumter aussehen.
Der Nachteil könnte sein, dass Sie die Inline-IDE-Fehlerprüfung und die Debugger-Bindungen vermasselt haben.
quelle
Ich bevorzuge den Ansatz, die Implementierung in eine separate Datei zu stellen und nur die Dokumentation und Deklarationen in der Header-Datei zu haben.
Vielleicht haben Sie diesen Ansatz in der Praxis nicht oft gesehen, weil Sie nicht an den richtigen Stellen gesucht haben ;-)
Oder - vielleicht liegt es daran, dass die Entwicklung der Software etwas mehr Aufwand erfordert. Aber für eine Klassenbibliothek lohnt sich diese Anstrengung, IMHO, und macht sich in einer viel einfacher zu verwendenden / lesbaren Bibliothek bezahlt.
Nehmen Sie zum Beispiel diese Bibliothek: https://github.com/SophistSolutions/Stroika/
Die gesamte Bibliothek wird mit diesem Ansatz geschrieben. Wenn Sie den Code durchsehen, werden Sie sehen, wie gut er funktioniert.
Die Header-Dateien sind ungefähr so lang wie die Implementierungsdateien, aber sie sind nur mit Deklarationen und Dokumentationen gefüllt.
Vergleichen Sie die Lesbarkeit von Stroika mit der Ihrer bevorzugten Standard-C ++ - Implementierung (gcc oder libc ++ oder msvc). Diese verwenden alle den Inline-In-Header-Implementierungsansatz, und obwohl sie sehr gut geschrieben sind, sind sie meiner Meinung nach nicht als lesbare Implementierungen.
quelle