Warum sollte ich keine CPP-Dateien einschließen und stattdessen einen Header verwenden?


146

Also beendete ich meine erste C ++ - Programmieraufgabe und erhielt meine Note. Aber laut der Bewertung habe ich Noten für verloren including cpp files instead of compiling and linking them. Mir ist nicht klar, was das bedeutet.

Beim Rückblick auf meinen Code habe ich beschlossen, keine Header-Dateien für meine Klassen zu erstellen, sondern alles in den CPP-Dateien zu tun (ohne Header-Dateien schien es gut zu funktionieren ...). Ich vermute, dass der Grader bedeutete, dass ich '#include "mycppfile.cpp" geschrieben habe;' in einigen meiner Dateien.

Meine Argumentation für #includedas Erstellen der CPP-Dateien war: - Alles, was in die Header-Datei aufgenommen werden sollte, befand sich in meiner CPP-Datei, also tat ich so, als wäre es wie eine Header-Datei Header-Dateien waren #includein den Dateien enthalten, daher habe ich dasselbe für meine CPP-Datei getan.

Was genau habe ich falsch gemacht und warum ist es schlecht?


36
Das ist eine wirklich gute Frage. Ich gehe davon aus, dass viele C ++ - Neulinge dadurch unterstützt werden.
Mia Clarke

Antworten:


174

Nach meinem besten Wissen kennt der C ++ - Standard keinen Unterschied zwischen Header- und Quelldateien. In Bezug auf die Sprache ist jede Textdatei mit Rechtscode dieselbe wie jede andere. Obwohl dies nicht illegal ist, werden durch das Einfügen von Quelldateien in Ihr Programm praktisch alle Vorteile beseitigt, die sich aus der Trennung Ihrer Quelldateien ergeben hätten.

Im Wesentlichen #includewird der Präprozessor angewiesen , die gesamte von Ihnen angegebene Datei zu übernehmen und in Ihre aktive Datei zu kopieren, bevor der Compiler sie in die Hände bekommt. Wenn Sie also alle Quelldateien in Ihr Projekt aufnehmen, gibt es grundsätzlich keinen Unterschied zwischen dem, was Sie getan haben, und dem Erstellen einer einzigen großen Quelldatei ohne jegliche Trennung.

"Oh, das ist keine große Sache. Wenn es läuft, ist es in Ordnung", höre ich dich weinen. Und in gewissem Sinne wären Sie richtig. Aber im Moment haben Sie es mit einem winzigen kleinen Programm und einer schönen und relativ unbelasteten CPU zu tun, um es für Sie zu kompilieren. Sie werden nicht immer so viel Glück haben.

Wenn Sie jemals in die Bereiche seriöser Computerprogrammierung eintauchen, werden Sie Projekte mit Zeilenzahlen sehen, die Millionen statt Dutzende erreichen können. Das sind viele Zeilen. Wenn Sie versuchen, eines davon auf einem modernen Desktop-Computer zu kompilieren, kann dies einige Stunden statt Sekunden dauern.

"Oh nein! Das klingt schrecklich! Kann ich dieses schreckliche Schicksal jedoch verhindern?!" Leider können Sie nicht viel dagegen tun. Wenn das Kompilieren Stunden dauert, dauert das Kompilieren Stunden. Aber das ist nur beim ersten Mal wirklich wichtig - wenn Sie es einmal kompiliert haben, gibt es keinen Grund, es erneut zu kompilieren.

Es sei denn, Sie ändern etwas.

Wenn Sie nun zwei Millionen Codezeilen zu einem riesigen Giganten zusammengeführt haben und eine einfache Fehlerbehebung durchführen müssen, z. B., x = y + 1müssen Sie alle zwei Millionen Zeilen erneut kompilieren, um dies zu testen. Und wenn Sie herausfinden, dass Sie x = y - 1stattdessen eine durchführen wollten, warten wieder zwei Millionen Kompilierungszeilen auf Sie. Das sind viele Stunden Zeitverschwendung, die man besser für etwas anderes verwenden könnte.

"Aber ich hasse es, unproduktiv zu sein! Wenn es nur eine Möglichkeit gäbe , bestimmte Teile meiner Codebasis einzeln zu kompilieren und sie danach irgendwie miteinander zu verknüpfen !" Theoretisch eine hervorragende Idee. Aber was ist, wenn Ihr Programm wissen muss, was in einer anderen Datei vor sich geht? Es ist unmöglich, Ihre Codebasis vollständig zu trennen, es sei denn, Sie möchten stattdessen eine Reihe winziger EXE-Dateien ausführen.

"Aber es muss doch möglich sein! Programmieren klingt ansonsten wie reine Folter! Was wäre, wenn ich einen Weg finden würde, die Schnittstelle von der Implementierung zu trennen ? Nehmen wir an, Sie nehmen gerade genug Informationen aus diesen unterschiedlichen Codesegmenten, um sie für den Rest des Programms zu identifizieren, und setzen sie ein sie stattdessen in einer Art Header- Datei? Und auf diese Weise kann ich die #include Präprozessor-Direktive verwenden , um nur die Informationen einzubringen, die zum Kompilieren erforderlich sind! "

Hmm. Sie könnten dort auf etwas sein. Lassen Sie mich wissen, wie das für Sie funktioniert.


13
Gute Antwort, Sir. Es war eine lustige Lektüre und leicht zu verstehen. Ich wünschte, mein Lehrbuch wäre so geschrieben.
Ialm

@veol Suche nach Head First-Buchreihe - Ich weiß allerdings nicht, ob sie eine C ++ - Version haben. headfirstlabs.com
Amarghosh

2
Dies ist (definitiv) der beste Wortlaut, den ich bisher gehört oder in Betracht gezogen habe. Justin Case, ein versierter Anfänger, hat ein Projekt mit einer Million Tastenanschlägen erreicht, das noch nicht ausgeliefert wurde, und ein lobenswertes "erstes Projekt", das das Licht der Anwendung in einer realen Benutzerbasis erblickt, hat ein Problem erkannt, das durch Schließungen behoben wurde. Klingt bemerkenswert ähnlich zu den fortgeschrittenen Zuständen der ursprünglichen Problemdefinition von OP abzüglich der "fast hundertmal codiert und kann nicht herausfinden, was für null (als kein Objekt) gegen null (als Neffe) zu tun ist, ohne die Programmierung durch Ausnahmen zu verwenden."
Nicholas Jordan

Natürlich fällt dies alles für Vorlagen auseinander, da die meisten Compiler das Schlüsselwort 'export' nicht unterstützen / implementieren.
KitsuneYMG

1
Ein weiterer Punkt ist, dass Sie viele hochmoderne Bibliotheken haben (wenn Sie an BOOST denken), die nur Header-Klassen verwenden ... Ho, warten Sie? Warum trennt ein erfahrener Programmierer die Schnittstelle nicht von der Implementierung? Ein Teil der Antwort könnte das sein, was Blindly gesagt hat, ein anderer Teil könnte sein, dass eine Datei besser als zwei ist, wenn es möglich ist, und ein anderer Teil ist, dass das Verknüpfen Kosten verursacht, die recht hoch sein können. Ich habe gesehen, dass Programme zehnmal schneller ausgeführt wurden, wenn die Quell- und Compileroptimierung direkt einbezogen wurde. Weil das Verknüpfen meistens die Optimierung blockiert.
Kriss

45

Dies ist wahrscheinlich eine detailliertere Antwort als Sie wollten, aber ich denke, eine anständige Erklärung ist gerechtfertigt.

In C und C ++ wird eine Quelldatei als eine Übersetzungseinheit definiert . Standardmäßig enthalten Header-Dateien Funktionsdeklarationen, Typdefinitionen und Klassendefinitionen. Die eigentlichen Funktionsimplementierungen befinden sich in Übersetzungseinheiten, dh CPP-Dateien.

Die Idee dahinter ist, dass Funktionen und Klassen- / Strukturelementfunktionen einmal kompiliert und zusammengestellt werden. Andere Funktionen können diesen Code dann von einer Stelle aus aufrufen, ohne Duplikate zu erstellen. Ihre Funktionen werden implizit als "extern" deklariert.

/* Function declaration, usually found in headers. */
/* Implicitly 'extern', i.e the symbol is visible everywhere, not just locally.*/
int add(int, int);

/* function body, or function definition. */
int add(int a, int b) 
{
   return a + b;
}

Wenn eine Funktion für eine Übersetzungseinheit lokal sein soll, definieren Sie sie als 'statisch'. Was bedeutet das? Wenn Sie Quelldateien mit externen Funktionen einschließen, werden Neudefinitionsfehler angezeigt, da der Compiler mehrmals auf dieselbe Implementierung stößt. Sie möchten also, dass alle Ihre Übersetzungseinheiten die Funktionsdeklaration sehen , nicht jedoch den Funktionskörper .

Wie kommt das alles am Ende zusammen? Das ist die Aufgabe des Linkers. Ein Linker liest alle Objektdateien, die von der Assembler-Phase generiert werden, und löst Symbole auf. Wie ich bereits sagte, ist ein Symbol nur ein Name. Zum Beispiel der Name einer Variablen oder einer Funktion. Wenn Übersetzungseinheiten, die Funktionen aufrufen oder Typen deklarieren, die Implementierung für diese Funktionen oder Typen nicht kennen, werden diese Symbole als ungelöst bezeichnet. Der Linker löst das nicht aufgelöste Symbol auf, indem er die Übersetzungseinheit, die das undefinierte Symbol enthält, mit der Einheit verbindet, die die Implementierung enthält. Puh. Dies gilt für alle extern sichtbaren Symbole, unabhängig davon, ob sie in Ihrem Code implementiert sind oder von einer zusätzlichen Bibliothek bereitgestellt werden. Eine Bibliothek ist eigentlich nur ein Archiv mit wiederverwendbarem Code.

Es gibt zwei bemerkenswerte Ausnahmen. Wenn Sie eine kleine Funktion haben, können Sie sie zunächst inline machen. Dies bedeutet, dass der generierte Maschinencode keinen externen Funktionsaufruf generiert, sondern buchstäblich direkt verkettet wird. Da sie normalerweise klein sind, spielt der Overhead keine Rolle. Sie können sich vorstellen, dass sie in ihrer Arbeitsweise statisch sind. So ist es sicher, Inline-Funktionen in Headern zu implementieren. Funktionsimplementierungen innerhalb einer Klassen- oder Strukturdefinition werden vom Compiler häufig auch automatisch eingefügt.

Die andere Ausnahme sind Vorlagen. Da der Compiler beim Instanziieren die gesamte Definition des Vorlagentyps sehen muss, ist es nicht möglich, die Implementierung wie bei eigenständigen Funktionen oder normalen Klassen von der Definition zu entkoppeln. Nun, vielleicht ist dies jetzt möglich, aber es hat lange gedauert, eine umfassende Compiler-Unterstützung für das Schlüsselwort "export" zu erhalten. Ohne Unterstützung für 'Export' erhalten Übersetzungseinheiten ihre eigenen lokalen Kopien von instanziierten Vorlagen-Typen und -Funktionen, ähnlich wie Inline-Funktionen funktionieren. Mit Unterstützung für 'Export' ist dies nicht der Fall.

Mit Ausnahme der beiden Ausnahmen finden es einige Leute "schöner", die Implementierungen von Inline-Funktionen, Vorlagenfunktionen und Vorlagen-Typen in CPP-Dateien zu platzieren und dann die CPP-Datei einzuschließen. Ob dies ein Header oder eine Quelldatei ist, spielt keine Rolle. Der Präprozessor kümmert sich nicht darum und ist nur eine Konvention.

Eine kurze Zusammenfassung des gesamten Prozesses vom C ++ - Code (mehrere Dateien) bis zur endgültigen ausführbaren Datei:

  • Der Präprozessor wird ausgeführt, der alle Anweisungen analysiert, die mit einem '#' beginnen. Die Direktive #include verkettet beispielsweise die enthaltene Datei mit inferior. Es werden auch Makros ersetzt und Token eingefügt.
  • Der eigentliche Compiler wird nach der Präprozessorphase in der Zwischentextdatei ausgeführt und gibt Assembler-Code aus.
  • Der Assembler wird in der Assembly-Datei ausgeführt und gibt Maschinencode aus. Diese wird normalerweise als Objektdatei bezeichnet und folgt dem binären ausführbaren Format des betreffenden Betriebssystems. Beispielsweise verwendet Windows das PE (Portable Executable Format), während Linux das Unix System V ELF-Format mit GNU-Erweiterungen verwendet. Zu diesem Zeitpunkt sind Symbole noch als undefiniert markiert.
  • Schließlich wird der Linker ausgeführt. Alle vorherigen Stufen wurden in der Reihenfolge auf jeder Übersetzungseinheit ausgeführt. Die Linker-Phase arbeitet jedoch mit allen generierten Objektdateien, die vom Assembler generiert wurden. Der Linker löst Symbole auf und macht viel Magie wie das Erstellen von Abschnitten und Segmenten, was von der Zielplattform und dem Binärformat abhängt. Programmierer müssen dies im Allgemeinen nicht wissen, aber es hilft sicherlich in einigen Fällen.

Auch dies war definitiv mehr, als Sie verlangt haben, aber ich hoffe, dass die Details Ihnen helfen, das Gesamtbild zu sehen.


2
Vielen Dank für Ihre gründliche Erklärung. Ich gebe zu, es macht noch nicht alles Sinn für mich und ich denke, ich muss Ihre Antwort noch einmal (und noch einmal) durchlesen.
Ialm

1
+1 für eine hervorragende Erklärung. Schade, dass es wahrscheinlich alle C ++ - Neulinge abschreckt. :)
GoldPseudo

1
Heh, fühl dich nicht schlecht, veol. Bei Stapelüberlauf ist die längste Antwort selten die beste Antwort.

int add(int, int);ist eine Funktion Erklärung . Der Prototyp Teil davon ist gerecht int, int. Alle Funktionen in C ++ haben jedoch einen Prototyp, sodass der Begriff nur in C wirklich Sinn macht. Ich habe Ihre Antwort auf diesen Effekt bearbeitet.
Melpomene

exportfor templates wurde 2011 aus der Sprache entfernt. Es wurde von Compilern nie wirklich unterstützt.
Melpomene

10

Die typische Lösung besteht darin, .hDateien nur für Deklarationen und .cppDateien für die Implementierung zu verwenden. Wenn Sie die Implementierung wiederverwenden müssen, fügen Sie die entsprechende .hDatei in die .cppDatei ein, in der die erforderliche Klasse / Funktion / was auch immer verwendet wird, und verknüpfen Sie sie mit einer bereits kompilierten .cppDatei (entweder eine .objDatei - normalerweise innerhalb eines Projekts verwendet - oder eine .lib-Datei - normalerweise verwendet zur Wiederverwendung aus mehreren Projekten). Auf diese Weise müssen Sie nicht alles neu kompilieren, wenn sich nur die Implementierung ändert.


6

Stellen Sie sich cpp-Dateien als Black Box und die .h-Dateien als Anleitungen zur Verwendung dieser Black Box vor.

Die CPP-Dateien können vorab kompiliert werden. Dies funktioniert in Ihnen nicht. # Schließen Sie sie ein, da der Code bei jeder Kompilierung tatsächlich in Ihr Programm "aufgenommen" werden muss. Wenn Sie nur den Header einfügen, kann er anhand der Header-Datei bestimmen, wie die vorkompilierte CPP-Datei verwendet wird.

Obwohl dies für Ihr erstes Projekt keinen großen Unterschied macht, werden Sie die Leute hassen, wenn Sie anfangen, große CPP-Programme zu schreiben, weil die Kompilierungszeiten explodieren werden.

Lesen Sie auch Folgendes : Header-Datei enthält Muster


Vielen Dank für das konkretere Beispiel. Ich habe versucht, Ihren Link zu lesen, aber jetzt bin ich verwirrt ... Was ist der Unterschied zwischen dem expliziten Einfügen eines Headers und einer Vorwärtsdeklaration?
Ialm

Dies ist ein großartiger Artikel. Veol, hier enthalten sie Header, in denen der Compiler Informationen zur Größe der Klasse benötigt. Die Vorwärtsdeklaration wird verwendet, wenn Sie nur Zeiger verwenden.
Pankajt

Vorwärtsdeklaration: int someFunction (int requiredValue); Beachten Sie die Verwendung von Typinformationen und (normalerweise) keine geschweiften Klammern. Dies teilt dem Compiler mit, dass Sie irgendwann eine Funktion benötigen, die ein int nimmt und ein int zurückgibt. Der Compiler kann anhand dieser Informationen einen Aufruf dafür reservieren. Das würde man als Vorwärtserklärung bezeichnen. Ausgefallene Compiler sollen in der Lage sein, die Funktion zu finden, ohne dies zu benötigen. Ein Header kann eine praktische Möglichkeit sein, eine Reihe von Vorwärtsdeklarationen zu deklarieren.
Nicholas Jordan

6

Header-Dateien enthalten normalerweise Deklarationen von Funktionen / Klassen, während CPP-Dateien die tatsächlichen Implementierungen enthalten. Zur Kompilierungszeit wird jede CPP-Datei in eine Objektdatei kompiliert (normalerweise die Erweiterung .o), und der Linker kombiniert die verschiedenen Objektdateien in der endgültigen ausführbaren Datei. Der Verknüpfungsprozess ist im Allgemeinen viel schneller als die Kompilierung.

Vorteile dieser Trennung: Wenn Sie eine der CPP-Dateien in Ihrem Projekt neu kompilieren, müssen Sie nicht alle anderen Dateien neu kompilieren. Sie erstellen einfach die neue Objektdatei für diese bestimmte CPP-Datei. Der Compiler muss sich die anderen CPP-Dateien nicht ansehen. Wenn Sie jedoch Funktionen in Ihrer aktuellen CPP-Datei aufrufen möchten, die in den anderen CPP-Dateien implementiert wurden, müssen Sie dem Compiler mitteilen, welche Argumente sie verwenden. Dies ist der Zweck des Einfügens der Header-Dateien.

Nachteile: Beim Kompilieren einer bestimmten CPP-Datei kann der Compiler nicht sehen, was sich in den anderen CPP-Dateien befindet. Es weiß also nicht, wie die Funktionen dort implementiert sind, und kann daher nicht so aggressiv optimieren. Aber ich denke, Sie müssen sich noch nicht darum kümmern (:


5

Die Grundidee, dass Header nur enthalten sind und CPP-Dateien nur kompiliert werden. Dies ist nützlicher, wenn Sie über viele CPP-Dateien verfügen. Das Neukompilieren der gesamten Anwendung, wenn Sie nur eine davon ändern, ist zu langsam. Oder wann die Funktionen in den Dateien abhängig voneinander starten. Sie sollten also Klassendeklarationen in Ihre Header-Dateien trennen, die Implementierung in CPP-Dateien belassen und ein Makefile (oder etwas anderes, je nachdem, welche Tools Sie verwenden) schreiben, um die CPP-Dateien zu kompilieren und die resultierenden Objektdateien in ein Programm zu verknüpfen.


3

Wenn Sie eine CPP-Datei in mehrere andere Dateien in Ihrem Programm einschließen, versucht der Compiler mehrmals, die CPP-Datei zu kompilieren, und generiert einen Fehler, da mehrere Implementierungen derselben Methoden vorhanden sind.

Das Kompilieren dauert länger (was bei großen Projekten zu einem Problem wird), wenn Sie Änderungen an #included cpp-Dateien vornehmen, wodurch die Neukompilierung aller # # Dateien, einschließlich dieser, erzwungen wird.

Fügen Sie einfach Ihre Deklarationen in Header-Dateien ein und fügen Sie diese ein (da sie eigentlich keinen Code per se generieren), und der Linker verbindet die Deklarationen mit dem entsprechenden CPP-Code (der dann nur einmal kompiliert wird).


Zusätzlich zu längeren Kompilierungszeiten treten also Probleme auf, wenn ich meine CPP-Datei in viele verschiedene Dateien einbinde, die die Funktionen in den enthaltenen CPP-Dateien verwenden.
Ialm

Ja, dies wird als Namespace-Kollision bezeichnet. Interessant ist hier, ob das Verknüpfen mit Bibliotheken Namespace-Probleme mit sich bringt. Im Allgemeinen stelle ich fest, dass Compiler bessere Kompilierungszeiten für den Umfang der Übersetzungseinheiten (alle in einer Datei) erzielen, was zu Namespace-Problemen führt - was zu einer erneuten Trennung führt. Sie können die Include-Datei in jede Übersetzungseinheit aufnehmen (soll) Es gibt sogar ein Pragma (einmal #pragma), das dies erzwingen soll, aber das ist eine Vermutung. Achten Sie darauf, sich nicht blindlings auf Bibliotheken (.O-Dateien) zu verlassen, da 32-Bit-Links nicht erzwungen werden.
Nicholas Jordan

2

Während es sicherlich möglich ist, das zu tun, was Sie getan haben, besteht die Standardpraxis darin, gemeinsam genutzte Deklarationen in Header-Dateien (.h) und Definitionen von Funktionen und Variablen - Implementierung - in Quelldateien (.cpp) einzufügen.

Als Konvention hilft dies, klar zu machen, wo sich alles befindet, und macht eine klare Unterscheidung zwischen Schnittstelle und Implementierung Ihrer Module. Dies bedeutet auch, dass Sie niemals überprüfen müssen, ob eine CPP-Datei in einer anderen enthalten ist, bevor Sie etwas hinzufügen, das beschädigt werden kann, wenn es in mehreren verschiedenen Einheiten definiert wurde.


2

Wiederverwendbarkeit, Architektur und Datenkapselung

Hier ist ein Beispiel:

Angenommen, Sie erstellen eine CPP-Datei, die eine einfache Form von Zeichenfolgenroutinen enthält, alle in einer Klasse mystring. Sie platzieren die Klassendeklaration dafür in einer mystring.h, die mystring.cpp in eine OBJ-Datei kompiliert

Jetzt fügen Sie in Ihr Hauptprogramm (z. B. main.cpp) den Header ein und verknüpfen ihn mit mystring.obj. Um mystring in Ihrem Programm zu verwenden, ist es Ihnen egal, wie mystring implementiert wird, da der Header angibt, was es tun kann

Wenn ein Kumpel Ihre mystring-Klasse verwenden möchte, geben Sie ihm mystring.h und mystring.obj. Er muss auch nicht unbedingt wissen, wie es funktioniert, solange es funktioniert.

Wenn Sie später mehr solche OBJ-Dateien haben, können Sie diese zu einer LIB-Datei kombinieren und stattdessen mit dieser verknüpfen.

Sie können auch entscheiden, die Datei mystring.cpp zu ändern und effektiver zu implementieren. Dies hat keine Auswirkungen auf Ihre main.cpp oder Ihr Buddy-Programm.


2

Wenn es für Sie funktioniert, ist daran nichts auszusetzen - außer dass es die Federn von Menschen zerzaust, die glauben, dass es nur einen Weg gibt, Dinge zu tun.

Viele der hier gegebenen Antworten beziehen sich auf Optimierungen für große Softwareprojekte. Dies sind gute Dinge, die Sie wissen sollten, aber es macht keinen Sinn, ein kleines Projekt so zu optimieren, als wäre es ein großes Projekt - das wird als "vorzeitige Optimierung" bezeichnet. Abhängig von Ihrer Entwicklungsumgebung kann das Einrichten einer Build-Konfiguration zur Unterstützung mehrerer Quelldateien pro Programm mit einer erheblichen zusätzlichen Komplexität verbunden sein.

Wenn im Laufe der Zeit, Projektverlauf und Sie feststellen , dass der Build - Prozess zu lange dauert, dann können Sie Refactoring Code mehrere Quelldateien zu verwenden , für eine schnellere inkrementelle Builds.

In mehreren Antworten wird die Trennung der Schnittstelle von der Implementierung erörtert. Dies ist jedoch keine inhärente Funktion von Include-Dateien, und es ist durchaus üblich, "Header" -Dateien einzuschließen, die ihre Implementierung direkt einbeziehen (selbst die C ++ - Standardbibliothek tut dies in erheblichem Maße).

Das einzige, was wirklich "unkonventionell" an dem war, was Sie getan haben, war, Ihre enthaltenen Dateien ".cpp" anstelle von ".h" oder ".hpp" zu benennen.


1

Wenn Sie ein Programm kompilieren und verknüpfen, kompiliert der Compiler zuerst die einzelnen CPP-Dateien und verknüpft sie dann (verbindet sie). Die Header werden niemals kompiliert, es sei denn, sie werden zuerst in eine CPP-Datei aufgenommen.

In der Regel sind Header Deklarationen und cpp Implementierungsdateien. In den Headern definieren Sie eine Schnittstelle für eine Klasse oder Funktion, lassen jedoch aus, wie Sie die Details tatsächlich implementieren. Auf diese Weise müssen Sie nicht jede CPP-Datei neu kompilieren, wenn Sie eine Änderung in einer vornehmen.


Wenn Sie die Implementierung aus der Header-Datei herauslassen, entschuldigen Sie mich, aber das klingt für mich wie eine Java-Schnittstelle, oder?
Gansub

1

Ich werde Ihnen vorschlagen, das groß angelegte C ++ - Software-Design von John Lakos durchzugehen . Im College schreiben wir normalerweise kleine Projekte, bei denen wir auf solche Probleme nicht stoßen. Das Buch unterstreicht die Bedeutung der Trennung von Schnittstellen und Implementierungen.

Header-Dateien haben normalerweise Schnittstellen, die nicht so häufig geändert werden sollen. In ähnlicher Weise hilft Ihnen ein Blick in Muster wie die Virtual Constructor-Sprache, das Konzept besser zu verstehen.

Ich lerne immer noch wie du :)


Danke für den Buchvorschlag. Ich weiß nicht, ob ich jemals in der Lage sein werde, große C ++ - Programme zu
erstellen

Es macht Spaß, große Programme zu programmieren, und für viele eine Herausforderung. Ich
fange

1

Es ist wie beim Schreiben eines Buches. Sie möchten fertige Kapitel nur einmal ausdrucken

Angenommen, Sie schreiben ein Buch. Wenn Sie die Kapitel in separaten Dateien ablegen, müssen Sie ein Kapitel nur ausdrucken, wenn Sie es geändert haben. Die Arbeit an einem Kapitel ändert nichts an den anderen.

Das Einbeziehen der CPP-Dateien ist aus Sicht des Compilers jedoch so, als würden Sie alle Kapitel des Buches in einer Datei bearbeiten. Wenn Sie es dann ändern, müssen Sie alle Seiten des gesamten Buches drucken, damit Ihr überarbeitetes Kapitel gedruckt wird. Bei der Objektcodegenerierung gibt es keine Option "Ausgewählte Seiten drucken".

Zurück zur Software: Ich habe Linux und Ruby src herumliegen. Ein grobes Maß für Codezeilen ...

     Linux       Ruby
   100,000    100,000   core functionality (just kernel/*, ruby top level dir)
10,000,000    200,000   everything 

Jede dieser vier Kategorien enthält viel Code, weshalb Modularität erforderlich ist. Diese Art von Codebasis ist überraschend typisch für reale Systeme.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.