Was sind die Vor- und Nachteile der Trennung von Deklaration und Definition wie in C ++?

8

In C ++ können Deklaration und Definition von Funktionen, Variablen und Konstanten wie folgt getrennt werden:

%Vor%

Tatsächlich ist dies bei der Definition von Klassen oft der Fall. Eine Klasse wird normalerweise mit ihren Mitgliedern in einer .h-Datei deklariert, und diese werden dann in einer entsprechenden .C-Datei definiert.

Was sind die Vorteile & amp; Nachteile dieses Ansatzes?

    
fluffels 14.03.2009, 11:18
quelle

11 Antworten

12

Historisch war das dem Compiler zu helfen. Sie mussten ihm die Liste der Namen geben, bevor er sie verwendete - ob dies die tatsächliche Verwendung war oder eine Forward-Deklaration (Cs Standard-Funktions-Prototyp nebenbei).

Moderne Compiler für moderne Sprachen zeigen, dass dies keine Notwendigkeit mehr ist, so dass C & amp; C ++ 's (sowie Objective-C und wahrscheinlich andere) Syntax hier ist historisches Gepäck. In der Tat ist dies eines der großen Probleme mit C ++, das selbst das Hinzufügen eines richtigen Modulsystems nicht lösen wird.

Nachteile sind: viele stark verschachtelte Include-Dateien (ich habe bereits Bäume eingezeichnet, sie sind überraschend groß) und Redundanz zwischen Deklaration und Definition - alles führt zu längeren Codierungszeiten und längeren Kompilierzeiten (je verglichen die Kompilierzeiten zwischen vergleichbare C ++ - und C # -Projekte? Dies ist einer der Gründe für den Unterschied). Header-Dateien müssen für Benutzer von von Ihnen bereitgestellten Komponenten bereitgestellt werden. Chancen von ODR-Verstößen Vertrauen in den Pre-Prozessor (viele moderne Sprachen benötigen keinen Pre-Prozessor-Schritt), wodurch Ihr Code zerbrechlicher und für Tools schwieriger parsen kann.

Vorteile: nicht viel. Man könnte argumentieren, dass man zu Dokumentationszwecken eine Liste von Funktionsnamen an einem Ort zusammenfasst - aber die meisten IDEs haben heutzutage eine Art Code-Faltungsfähigkeit, und Projekte jeder Größe sollten sowieso Doc-Generatoren (wie Doxygen) verwenden. Mit einer sauberen, vorprozessorlosen, modulbasierten Syntax ist es für Werkzeuge einfacher, Ihrem Code zu folgen und dies und mehr zu liefern, daher denke ich, dass dieser "Vorteil" nur umstritten ist.

    
philsquared 14.03.2009, 12:36
quelle
9

Es ist ein Artefakt, wie C / C ++ - Compiler funktionieren.

Wenn eine Quelldatei kompiliert wird, ersetzt der Präprozessor jede # include-Anweisung durch den Inhalt der enthaltenen Datei. Erst danach versucht der Compiler das Ergebnis dieser Verkettung zu interpretieren.

Der Compiler geht dann von Anfang bis Ende über dieses Ergebnis und versucht, jede Aussage zu validieren. Wenn eine Codezeile eine Funktion aufruft, die zuvor nicht definiert wurde, gibt sie auf.

Es gibt jedoch ein Problem damit, wenn es zu gegenseitig rekursiven Funktionsaufrufen kommt:

%Vor%

Hier wird foo nicht kompiliert, da bar unbekannt ist. Wenn Sie die beiden Funktionen umschalten, wird bar nicht kompiliert, da foo unbekannt ist.

Wenn Sie die Deklaration und die Definition jedoch trennen, können Sie die Funktionen wie gewünscht bestellen:

%Vor%

Hier, wenn der Compiler foo verarbeitet, kennt er bereits die Signatur einer Funktion namens bar und ist glücklich.

Natürlich können Compiler anders arbeiten, aber so arbeiten sie in C, C ++ und zu einem gewissen Grad in Objective-C.

Nachteile:

Keine direkt. Wenn Sie sowieso C / C ++ verwenden, ist dies der beste Weg, Dinge zu tun. Wenn Sie eine Sprache / einen Compiler auswählen können, können Sie vielleicht einen aussuchen, wo das kein Problem ist. Das einzige, was bei der Deklaration von Deklarationen in Header-Dateien zu beachten ist, ist die Vermeidung von gegenseitig rekursiven # include-Anweisungen - aber dafür gibt es auch Wächter.

Vorteile:

  • Kompilierungsgeschwindigkeit: Da alle eingeschlossenen Dateien verkettet und dann analysiert werden, reduziert die Menge und Komplexität des Codes in den enthaltenen Dateien die Kompilierungszeit.
  • Code-Duplizierung / Inlining vermeiden: Wenn Sie eine Funktion vollständig in einer Header-Datei definieren, enthält jede Objektdatei, die diesen Header enthält und auf diese Funktion verweist, ihre eigene Version dieser Funktion. Als Randbemerkung müssen Sie die vollständige Definition in die Header-Datei (bei den meisten Compilern) einfügen, wenn Sie möchten.
  • Kapselung / Klarheit: Eine gut definierte Klasse / ein Satz von Funktionen sowie einige Dokumentationen sollten ausreichen, damit andere Entwickler Ihren Code verwenden können. Es gibt (im Idealfall) keine Notwendigkeit für sie zu verstehen, wie der Code funktioniert - also warum müssen sie es durchforsten? (Das Gegenargument, dass es für sie nützlich sein kann, auf die Implementierung zuzugreifen, steht bei Bedarf natürlich .)

Und natürlich, wenn Sie überhaupt keine Funktion offenlegen wollen, können Sie es in der Regel immer noch in der Implementierungsdatei und nicht in der Kopfzeile definieren.

    
unwesen 14.03.2009 12:52
quelle
3

Es gibt zwei Hauptvorteile bei der Trennung von Deklaration und Definition in C ++ - Header- und Quelldateien. Das erste ist, dass Sie Probleme mit der Definitionsregel vermeiden, wenn Ihre Klasse / Funktionen / was auch immer #include d mehr sind als ein Ort. Zweitens, indem Sie die Dinge auf diese Weise tun, trennen Sie die Schnittstelle und die Implementierung. Benutzer Ihrer Klasse oder Bibliothek müssen nur Ihre Header-Datei sehen, um Code zu schreiben, der sie verwendet. Du kannst diesen Schritt auch mit dem Pimpl Idiom weiter machen und es so machen, dass der Benutzercode nicht muss Kompilieren Sie jedes Mal neu, wenn sich die Bibliotheksimplementierung ändert.

Sie haben bereits den Nachteil der Codewiederholung zwischen den Dateien .h und .cpp erwähnt. Vielleicht habe ich zu lange C ++ Code geschrieben, aber ich glaube nicht, dass es das schlecht ist. Sie müssen den Benutzercode jedes Mal ändern, wenn Sie eine Funktionssignatur ändern, also was ist eine weitere Datei? Es ist nur ärgerlich, wenn Sie zum ersten Mal eine Klasse schreiben, und Sie müssen aus der Kopfzeile in die neue Quelldatei kopieren und einfügen.

Der andere Nachteil in der Praxis ist, dass Sie, um guten Code zu schreiben (und zu debuggen!), der eine Bibliothek eines Drittanbieters verwendet, normalerweise darin zu sehen. Das bedeutet, dass Sie auf den Quellcode zugreifen können, auch wenn Sie ihn nicht ändern können. Wenn alles, was Sie haben, eine Header-Datei und eine kompilierte Objektdatei ist, kann es sehr schwierig sein, zu entscheiden, ob der Fehler Ihr oder ihr Fehler ist. Wenn Sie sich die Quelle ansehen, erhalten Sie Einblick in die richtige Verwendung und Erweiterung einer Bibliothek, die in der Dokumentation möglicherweise nicht behandelt wird. Nicht jeder versendet eine MSDN mit ihrer Bibliothek. Und großartige Softwareentwickler haben die unangenehme Angewohnheit, Dinge mit Ihrem Code zu tun, von dem Sie nie geträumt hätten. ; -)

    
Michael Kristofik 14.03.2009 13:16
quelle
2

Der Standard erfordert, dass eine Deklaration beim Verwenden einer Funktion im Gültigkeitsbereich sein muss. Dies bedeutet, dass der Compiler in der Lage sein sollte, anhand eines Prototyps (der Deklaration in einer Header-Datei) zu überprüfen, was Sie an ihn übergeben. Außer natürlich für Funktionen, die variadic sind - solche Funktionen validieren keine Argumente.

Denken Sie an C, wenn dies nicht erforderlich war. Zu dieser Zeit behandelten Compiler keine Rückgabetypspezifikation, die auf int voreingestellt werden sollte. Angenommen, Sie hatten eine Funktion foo (), die einen Zeiger auf void zurückgab. Da Sie jedoch keine Deklaration hatten, wird der Compiler denken, dass er eine ganze Zahl zurückgeben muss. Auf einigen Motorola-Systemen würden beispielsweise Integer und Zeiger in verschiedenen Registern zurückgegeben werden. Jetzt verwendet der Compiler nicht mehr das richtige Register und gibt stattdessen den Zeiger an eine Ganzzahl in dem anderen Register zurück. In dem Moment, in dem du versuchst, mit diesem Zeiger zu arbeiten, bricht die Hölle los.

Das Deklarieren von Funktionen innerhalb der Kopfzeile ist in Ordnung. Aber denken Sie daran, wenn Sie in der Kopfzeile deklarieren und definieren, stellen Sie sicher, dass sie inline sind. Eine Möglichkeit, dies zu erreichen, besteht darin, die Definition in die Klassendefinition einzufügen. Andernfalls fügen Sie das inline -Schlüsselwort voran. Wenn der Header in mehreren Implementierungsdateien enthalten ist, werden Sie andernfalls auf ODR-Verletzung stoßen.

    
dirkgently 14.03.2009 11:26
quelle
1

Sie haben grundsätzlich zwei Ansichten über die Klasse / Funktion / was auch immer:

Die Deklaration, in der Sie den Namen, die Parameter und die Mitglieder (im Falle einer Struktur / Klasse) und die Definition, in der Sie definieren, was die Funktionen tun, deklarieren.

Zu den Nachteilen gehört die Wiederholung, aber ein großer Vorteil ist, dass Sie Ihre Funktion als int foo(float f) deklarieren können und die Details in der Implementierung (= Definition) belassen, sodass jeder, der Ihre Funktion foo verwenden möchte, nur Ihre Header-Datei enthält und Links zu Ihrer Bibliothek / Objektdatei, so müssen sowohl Bibliotheksbenutzer als auch Compiler sich nur um die definierte Schnittstelle kümmern, was das Verständnis der Schnittstellen erleichtert und die Kompilierzeiten beschleunigt.

    
tstenner 14.03.2009 13:27
quelle
1

Ein Vorteil, den ich noch nicht gesehen habe: API

Jede Bibliothek oder jeder Drittanbieter-Code, der NICHT open source (d. h. proprietär) ist, wird nicht zusammen mit der Distribution implementiert. Die meisten Unternehmen sind einfach nicht damit zufrieden, Quellcode zu vergeben. Die einfache Lösung, verteilen Sie einfach die Klassendeklarationen und Funktionssignaturen, die die Verwendung der DLL ermöglichen.

Disclaimer: Ich sage nicht, ob es richtig, falsch oder gerechtfertigt ist, ich sage nur, dass ich es oft gesehen habe.

    
Dashogun 16.03.2009 20:11
quelle
0

Vorteil

Klassen können aus anderen Dateien referenziert werden, indem Sie einfach die Deklaration einbeziehen. Definitionen können später im Kompilierungsprozess verknüpft werden.

    
fluffels 14.03.2009 11:19
quelle
0

Ein großer Vorteil von Forward-Deklarationen ist, dass Sie bei sorgfältiger Verwendung die Abhängigkeiten zwischen den Modulen für die Kompilierung reduzieren können.

Wenn ClassA.h auf ein Datenelement in ClassB.h verweisen muss, können Sie in ClassA.h häufig nur Vorwärtsreferenzen verwenden und ClassB.h in ClassA.cc und nicht in ClassA.h einbeziehen eine Kompilierzeitabhängigkeit.

Für große Systeme kann dies eine große Zeitersparnis bei einem Build bedeuten.

    
Andrew 14.03.2009 12:13
quelle
0
  1. Trennung bietet eine saubere, übersichtliche Ansicht von Programmelementen.
  2. Möglichkeit zum Erstellen und Verknüpfen von Binärmodulen / Bibliotheken ohne Angabe von Quellen.
  3. Verknüpfen Sie Binärdateien ohne Neukompilierung von Quellen.
qwer 16.03.2009 19:56
quelle
0

Wenn dies korrekt durchgeführt wird, reduziert diese Trennung die Kompilierzeiten, wenn nur die Implementierung geändert wurde.

    
Agnel Kurian 16.03.2009 23:23
quelle
-1

Nachteil

Dies führt zu vielen Wiederholungen. Der Großteil der Funktionssignaturen muss in zwei oder mehr (wie von Paulious angemerkt) platziert werden.

    
fluffels 14.03.2009 11:20
quelle