it-swarm.com.de

Warum müssen wir die .h einfügen, während alles funktioniert, wenn nur die .cpp-Datei eingeschlossen wird?

Warum müssen wir beide .h und .cpp Dateien, während wir es nur durch Einfügen der .cpp Datei?

Zum Beispiel: Erstellen eines file.h enthält Deklarationen und erstellt dann ein file.cpp enthält Definitionen und enthält beide in main.cpp.

Alternativ: Erstellen eines file.cpp mit Deklaration/Definitionen (keine Prototypen) einschließlich in main.cpp.

Beide arbeiten für mich. Ich kann den Unterschied nicht sehen. Vielleicht hilft ein Einblick in den Kompilierungs- und Verknüpfungsprozess.

19
reaffer

Während Sie wie erwähnt .cpp Dateien einschließen können, ist dies eine schlechte Idee.

Wie Sie bereits erwähnt haben, gehören Deklarationen in Header-Dateien. Diese verursachen keine Probleme, wenn sie in mehreren Kompilierungseinheiten enthalten sind, da sie keine Implementierungen enthalten. Das mehrmalige Einfügen der Definition einer Funktion oder eines Klassenmitglieds verursacht normalerweise ein Problem (aber nicht immer), da der Linker verwirrt wird und einen Fehler auslöst.

Was passieren sollte, ist, dass jede .cpp - Datei Definitionen für eine Teilmenge des Programms enthält, z. B. eine Klasse, eine logisch organisierte Gruppe von Funktionen, globale statische Variablen (wenn überhaupt sparsam verwenden) usw.

Jede Kompilierungseinheit (.cpp) Enthält dann alle Deklarationen, die zum Kompilieren der darin enthaltenen Definitionen erforderlich sind. Es verfolgt die Funktionen und Klassen, auf die es verweist, die es jedoch nicht enthält, sodass der Linker sie später auflösen kann, wenn er den Objektcode zu einer ausführbaren Datei oder Bibliothek kombiniert.

Beispiel

  • Foo.h -> enthält Deklaration (Schnittstelle) für die Klasse Foo.
  • Foo.cpp -> enthält Definition (Implementierung) für die Klasse Foo.
  • Main.cpp -> enthält Hauptmethode, Programmeinstiegspunkt. Dieser Code instanziiert ein Foo und verwendet es.

Sowohl Foo.cpp Als auch Main.cpp Müssen Foo.h Einschließen. Foo.cpp Benötigt es, weil es den Code definiert, der die Klassenschnittstelle unterstützt, also muss es wissen, was diese Schnittstelle ist. Main.cpp Benötigt es, weil es ein Foo erstellt und sein Verhalten aufruft. Daher muss es wissen, was dieses Verhalten ist, wie groß ein Foo im Speicher ist und wie es seine Funktionen findet usw., aber es braucht es nicht die eigentliche Implementierung gerade noch.

Der Compiler generiert Foo.o Aus Foo.cpp, Das den gesamten Foo-Klassencode in kompilierter Form enthält. Es generiert auch Main.o, Das die Hauptmethode und ungelöste Verweise auf die Klasse Foo enthält.

Jetzt kommt der Linker, der die beiden Objektdateien Foo.o Und Main.o Zu einer ausführbaren Datei kombiniert. Es sieht die ungelösten Foo-Referenzen in Main.o, Aber es sieht, dass Foo.o Die notwendigen Symbole enthält, so dass es sozusagen "die Punkte verbindet". Ein Funktionsaufruf in Main.o Ist jetzt mit dem tatsächlichen Speicherort des kompilierten Codes verbunden, sodass das Programm zur Laufzeit zum richtigen Speicherort springen kann.

Wenn Sie die Datei Foo.cpp In Main.cpp Eingefügt hätten, gäbe es zwei Definitionen der Klasse Foo. Der Linker würde dies sehen und sagen: "Ich weiß nicht, welchen ich auswählen soll, also ist dies ein Fehler." Der Kompilierungsschritt wäre erfolgreich, die Verknüpfung jedoch nicht. (Es sei denn, Sie kompilieren Foo.cpp Nicht, aber warum befindet es sich dann in einer separaten .cpp - Datei?)

Schließlich ist die Idee verschiedener Dateitypen für einen C/C++ - Compiler irrelevant. Es kompiliert "Textdateien", die hoffentlich gültigen Code für die gewünschte Sprache enthalten. Manchmal kann es die Sprache anhand der Dateierweiterung erkennen. Wenn Sie beispielsweise eine .c - Datei ohne Compileroptionen kompilieren, wird C angenommen, während eine Erweiterung von .cc Oder .cpp Anzeigt, dass C++ angenommen werden soll. Ich kann einem Compiler jedoch leicht sagen, dass er eine .h - oder sogar .docx - Datei als C++ kompilieren soll, und er gibt eine Objektdatei (.o) Aus, wenn sie gültigen C++ - Code enthält im Klartextformat. Diese Erweiterungen kommen eher dem Programmierer zugute. Wenn ich Foo.h Und Foo.cpp Sehe, gehe ich sofort davon aus, dass die erste die Deklaration der Klasse und die zweite die Definition enthält.

30
user22815

Lesen Sie mehr über die Rolle des C- und C++ - Präprozessors , der konzeptionell die erste "Phase" des C- oder C++ - Compilers ist (historisch gesehen war es ein separates Programm /lib/cpp; Leistungsgründe, warum es in den Compiler integriert ist cc1 oder cc1plus). Lesen Sie insbesondere die Dokumentation des Präprozessors GNU cpp . In der Praxis verarbeitet der Compiler Ihre Kompilierungseinheit (oder Übersetzung) also konzeptionell zuerst vor unit ) und arbeite dann an dem vorverarbeiteten Formular.

Sie müssen wahrscheinlich immer die Header-Datei file.h Einschließen, wenn sie enthält (wie durch Konventionen vorgegeben und Gewohnheiten):

  • makrodefinitionen
  • typdefinitionen (z. B. typedef, struct, class usw., ...)
  • definitionen von static inline Funktionen
  • deklarationen externer Funktionen.

Beachten Sie, dass es eine Frage der Konventionen (und der Zweckmäßigkeit) ist, diese in eine Header-Datei einzufügen.

Natürlich benötigt Ihre Implementierung file.cpp All das oben Genannte, also möchte Sie zuerst #include "file.h".

Dies ist eine Konvention (aber eine sehr häufige). Sie können Header-Dateien vermeiden und deren Inhalt kopieren und in Implementierungsdateien (d. H. Übersetzungseinheiten) einfügen. Aber das wollen Sie nicht (außer vielleicht, wenn Ihr C- oder C++ - Code automatisch generiert wird; dann könnten Sie das Generatorprogramm dazu bringen, dieses Kopieren und Einfügen durchzuführen und die Rolle des Präprozessors nachzuahmen).

Der Punkt ist, dass der Präprozessor textual Nur Operationen ausführt. Sie können es (im Prinzip) vollständig durch Kopieren und Einfügen vermeiden oder durch einen anderen "Präprozessor" oder C-Code-Generator (wie gpp oder m4 ) ersetzen.

Ein weiteres Problem besteht darin, dass die aktuellen C oder C++) Standards mehrere Standardheader definieren. Die meisten Implementierungen implementieren diese Standard Header wirklich als (implementierungsspezifisch) Dateien, aber ich glaube, dass es einer konformen Implementierung möglich wäre, Standard-Includes (wie) zu implementieren #include <stdio.h> Für C oder #include <vector> Für C++) mit einigen Zaubertricks (z. B. unter Verwendung einer Datenbank oder einiger Informationen inside des Compilers).

Wenn Sie --- ([~ # ~] gcc [~ # ~] Compiler verwenden (z. B. gcc oder g++), Können Sie das Flag -H Verwenden, um abzurufen über jede Aufnahme informiert und die -C -E - Flags, um das vorverarbeitete Formular zu erhalten. Natürlich gibt es viele andere Compiler-Flags, die sich auf die Vorverarbeitung auswirken (z. B. -I /some/dir/ Zum Hinzufügen von /some/dir/ Zum Durchsuchen der enthaltenen Dateien und -D Zum Vordefinieren eines Präprozessor-Makros usw. usw.). .).

NB. Zukünftige Versionen von C++ (vielleicht C++ 2 , vielleicht sogar später) haben möglicherweise C++ - Module .

Aufgrund des Build-Modells mit mehreren Einheiten von C++ benötigen Sie eine Möglichkeit, Code in Ihrem Programm nur einmal anzuzeigen (Definitionen), und Sie benötigen eine Möglichkeit, Code in jeder Übersetzungseinheit Ihres Programms anzuzeigen (Deklarationen).

Daraus entsteht die C++ - Header-Sprache. Es ist Konvention aus einem Grund.

Sie können Ihr gesamtes Programm in eine einzelne Übersetzungseinheit kopieren, dies führt jedoch zu Problemen bei der Wiederverwendung von Code, beim Testen von Einheiten und bei der Behandlung von Abhängigkeiten zwischen Modulen. Es ist auch nur ein großes Durcheinander.

Die ausgewählte Antwort von Warum müssen wir eine Header-Datei schreiben? ist eine vernünftige Erklärung, aber ich wollte zusätzliche Details hinzufügen.

Es scheint, dass das Rationale für Header-Dateien beim Lehren und Diskutieren von C/C++ verloren geht.

Die Header-Datei bietet eine Lösung für zwei Probleme bei der Anwendungsentwicklung:

  1. Trennung von Schnittstelle und Implementierung
  2. Verbesserte Kompilierungs-/Verknüpfungszeiten für große Programme

C/C++ kann von kleinen Programmen zu sehr großen Programmen mit mehreren Millionen Zeilen und mehreren tausend Dateien skaliert werden. Die Anwendungsentwicklung kann von Teams eines Entwicklers auf Hunderte von Entwicklern skaliert werden.

Sie können als Entwickler mehrere Hüte tragen. Insbesondere können Sie der Benutzer einer Schnittstelle zu Funktionen und Klassen sein, oder Sie können der Schreiber einer Schnittstelle von Funktionen und Klassen sein.

Wenn Sie eine Funktion verwenden, müssen Sie die Funktionsschnittstelle kennen, welche Parameter verwendet werden sollen, was die Funktionen zurückgeben und was die Funktion tut. Dies kann leicht in der Header-Datei dokumentiert werden, ohne jemals die Implementierung zu betrachten. Wann haben Sie die Implementierung von printf gelesen? Kaufen wir benutzen es jeden Tag.

Wenn Sie Entwickler einer Schnittstelle sind, ändert der Hut die andere Richtung. Die Header-Datei enthält die Deklaration der öffentlichen Schnittstelle. Die Header-Datei definiert, was eine andere Implementierung benötigt, um diese Schnittstelle zu verwenden. Interne und private Informationen zu dieser neuen Schnittstelle werden (und sollten) nicht in der Header-Datei deklariert. Die öffentliche Header-Datei sollte alles sein, was jemand zur Verwendung des Moduls benötigt.

Für die Entwicklung in großem Maßstab kann das Kompilieren und Verknüpfen lange dauern. Von vielen Minuten bis zu vielen Stunden (sogar zu vielen Tagen!). Die Aufteilung der Software in Schnittstellen (Header) und Implementierungen (Quellen) bietet eine Methode, mit der nur Dateien kompiliert werden können, die kompiliert werden müssen, anstatt alles neu zu erstellen.

Darüber hinaus können Entwickler mithilfe von Header-Dateien eine Bibliothek (bereits kompiliert) sowie eine Header-Datei bereitstellen. Andere Benutzer der Bibliothek sehen möglicherweise nie die richtige Implementierung, können die Bibliothek jedoch weiterhin mit der Header-Datei verwenden. Sie tun dies jeden Tag mit der C/C++ - Standardbibliothek.

Selbst wenn Sie eine kleine Anwendung entwickeln, ist die Verwendung umfangreicher Softwareentwicklungstechniken eine gute Angewohnheit. Wir müssen uns aber auch daran erinnern, warum wir diese Gewohnheiten anwenden.

0
Bill Door

Sie können sich auch fragen, warum Sie nicht einfach Ihren gesamten Code in eine Datei einfügen.

Die einfachste Antwort ist die Codepflege.

Es gibt Zeiten, in denen es sinnvoll ist, eine Klasse zu erstellen:

  • alle in einem Header eingefügt
  • alles innerhalb einer Kompilierungseinheit und überhaupt kein Header.

Die Zeit, in der es sinnvoll ist, einen Header vollständig inline zu setzen, ist, wenn die Klasse wirklich eine Datenstruktur mit einigen grundlegenden Get- und Setzern und möglicherweise einem Konstruktor ist, der Werte verwendet, um seine Mitglieder zu initialisieren.

(Vorlagen, die alle inline sein müssen, sind ein etwas anderes Problem).

Die andere Möglichkeit, eine Klasse innerhalb eines Headers zu erstellen, besteht darin, dass Sie die Klasse möglicherweise in mehreren Projekten verwenden und insbesondere das Verknüpfen in Bibliotheken vermeiden möchten.

Eine Zeit, in der Sie möglicherweise eine ganze Klasse in eine Kompilierungseinheit aufnehmen und deren Header überhaupt nicht verfügbar machen, ist:

  • Eine "impl" -Klasse, die nur von der Klasse verwendet wird, die sie implementiert. Es ist ein Implementierungsdetail dieser Klasse und wird extern nicht verwendet.

  • Eine Implementierung einer abstrakten Basisklasse, die von einer Factory-Methode erstellt wird, die einen Zeiger/eine Referenz/einen intelligenten Zeiger an die Basisklasse zurückgibt. Die Factory-Methode würde von der Klasse selbst nicht verfügbar gemacht. (Wenn die Klasse über eine Instanz verfügt, die sich über eine statische Instanz in einer Tabelle registriert, muss sie nicht einmal über eine Factory verfügbar gemacht werden.).

  • Eine "Funktor" -Typklasse.

Mit anderen Worten, wenn Sie nicht möchten, dass jemand den Header einfügt.

Ich weiß, was Sie vielleicht denken ... Wenn es nur um die Wartbarkeit geht, können Sie durch Einfügen der CPP-Datei (oder eines vollständig inline-Headers) eine Datei einfach bearbeiten, um den Code zu "finden" und einfach neu zu erstellen.

Bei "Wartbarkeit" sieht der Code jedoch nicht nur ordentlich aus. Es geht um die Auswirkungen des Wandels. Es ist allgemein bekannt, dass, wenn Sie einen Header unverändert lassen und nur eine Implementierung ändern (.cpp) Datei, es ist nicht erforderlich, die andere Quelle neu zu erstellen, da es keine Nebenwirkungen geben sollte.

Dies macht es "sicherer", solche Änderungen vorzunehmen, ohne sich um den Anstoßeffekt sorgen zu müssen, und genau das ist unter "Wartbarkeit" zu verstehen.

0
CashCow