Viele Sprachen wie Java, C # trennen die Deklaration nicht von der Implementierung. C # hat ein Konzept der Teilklasse, aber Implementierung und Deklaration bleiben weiterhin in derselben Datei.
Warum hat C ++ nicht das gleiche Modell? Ist es praktischer, Header-Dateien zu haben?
Ich beziehe mich auf aktuelle und kommende Versionen des C ++ - Standards.
c++
header-files
Sasha
quelle
quelle
Antworten:
Ich wechsle routinemäßig zwischen C # und C ++, und das Fehlen von Header-Dateien in C # ist einer meiner größten Probleme. Ich kann mir eine Header-Datei ansehen und alles lernen, was ich über eine Klasse wissen muss - wie ihre Mitgliedsfunktionen heißen, ihre aufrufende Syntax usw. -, ohne durch Seiten des Codes blättern zu müssen, der die Klasse implementiert.
Und ja, ich kenne Teilklassen und # Regionen, aber es ist nicht dasselbe. Teilklassen verschlimmern das Problem tatsächlich, da eine Klassendefinition auf mehrere Dateien verteilt ist. Was #regions angeht, scheinen sie nie so erweitert zu werden, wie ich es mir für das, was ich gerade mache, gewünscht habe. Daher muss ich Zeit damit verbringen, diese kleinen Pluspunkte zu erweitern, bis ich die richtige Ansicht habe.
Wenn Visual Studios Intellisense für C ++ besser funktionieren würde, hätte ich vielleicht keinen zwingenden Grund, so oft auf .h-Dateien zu verweisen, aber selbst in VS2008 kann C ++ 'Intellisense C # nicht berühren
quelle
Abwärtskompatibilität - Header-Dateien werden nicht entfernt, da dies die Abwärtskompatibilität beeinträchtigen würde.
quelle
Header-Dateien ermöglichen eine unabhängige Kompilierung. Sie müssen nicht auf die Implementierungsdateien zugreifen oder diese haben, um eine Datei zu kompilieren. Dies kann verteilte Builds vereinfachen.
Dadurch können SDKs auch etwas einfacher erstellt werden. Sie können nur die Header und einige Bibliotheken bereitstellen. Es gibt natürlich Möglichkeiten, die andere Sprachen verwenden.
quelle
Sogar Bjarne Stroustrup hat Header-Dateien als Kludge bezeichnet.
Aber ohne ein Standard-Binärformat, das die erforderlichen Metadaten enthält (wie Java-Klassendateien oder .NET PE-Dateien), sehe ich keine Möglichkeit, die Funktion zu implementieren. Ein abgespeckter ELF oder eine a.out-Binärdatei enthält nicht viele Informationen, die Sie extrahieren müssten. Und ich glaube nicht, dass die Informationen jemals in Windows XCOFF-Dateien gespeichert werden.
quelle
C wurde erstellt, um das Schreiben eines Compilers zu vereinfachen. Es macht eine Menge Sachen, die auf diesem einen Prinzip basieren. Zeiger dienen nur dazu, das Schreiben eines Compilers zu vereinfachen, ebenso wie Header-Dateien. Viele der auf C ++ übertragenen Dinge basieren auf der Kompatibilität mit diesen Funktionen, die implementiert wurden, um das Schreiben des Compilers zu vereinfachen.
Eigentlich ist es eine gute Idee. Als C erstellt wurde, waren C und Unix eine Art Paar. C portierte Unix, Unix lief C. Auf diese Weise konnten sich C und Unix schnell von Plattform zu Plattform verbreiten, während ein auf Assembly basierendes Betriebssystem für die Portierung komplett neu geschrieben werden musste.
Das Konzept, eine Schnittstelle in einer Datei anzugeben und die Implementierung in einer anderen, ist überhaupt keine schlechte Idee, aber das sind keine C-Header-Dateien. Sie sind lediglich eine Möglichkeit, die Anzahl der Durchgänge zu beschränken, die ein Compiler durch Ihren Quellcode ausführen muss, und eine begrenzte Abstraktion des Vertrags zwischen Dateien zu ermöglichen, damit diese kommunizieren können.
Diese Elemente, Zeiger, Header-Dateien usw. bieten keinen wirklichen Vorteil gegenüber einem anderen System. Wenn Sie mehr Aufwand in den Compiler investieren, können Sie ein Referenzobjekt so einfach kompilieren wie einen Zeiger auf genau denselben Objektcode. Dies ist, was C ++ jetzt tut.
C ist eine großartige, einfache Sprache. Es hatte einen sehr begrenzten Funktionsumfang, und Sie konnten ohne großen Aufwand einen Compiler schreiben. Das Portieren ist im Allgemeinen trivial! Ich versuche nicht zu sagen, dass es eine schlechte Sprache ist oder so, es ist nur so, dass Cs Hauptziele bei seiner Erstellung möglicherweise Reste in der Sprache hinterlassen, die jetzt mehr oder weniger unnötig sind, aber aus Kompatibilitätsgründen beibehalten werden.
Es scheint, dass einige Leute nicht wirklich glauben, dass C für Port Unix geschrieben wurde, also hier: ( von )
Hier ist ein perfektes Beispiel dafür, was ich meine. Aus den Kommentaren:
Du hast recht. Um die Indirektion zu implementieren, sind Zeiger die einfachste zu implementierende Abstraktion. In keiner Weise sind sie so einfach zu verstehen oder zu verwenden. Arrays sind viel einfacher.
Das Problem? Um Arrays so effizient wie Zeiger zu implementieren, müssen Sie Ihrem Compiler so ziemlich einen RIESIGEN Codestapel hinzufügen.
Es gibt keinen Grund, warum sie C nicht ohne Zeiger entworfen haben könnten, aber mit Code wie diesem:
int i=0; while(src[++i]) dest[i]=src[i];
Es wird viel Aufwand (seitens der Compiler) erfordern, die expliziten i + src- und i + dest-Ergänzungen herauszufiltern und denselben Code zu erstellen, den dies ergeben würde:
while(*(dest++) = *(src++)) ;
Das Herausrechnen dieser Variablen "i" nach der Tatsache ist HARD. Neue Compiler können das, aber damals war es einfach nicht möglich, und das Betriebssystem, das auf dieser beschissenen Hardware läuft, brauchte solche kleinen Optimierungen.
Jetzt benötigen nur noch wenige Systeme diese Art der Optimierung (ich arbeite auf einer der langsamsten Plattformen - Kabel-Set-Top-Boxen, und die meisten unserer Inhalte befinden sich in Java) und in den seltenen Fällen, in denen Sie sie benötigen, die neuen C-Compiler sollte klug genug sein, um diese Art der Konvertierung selbst durchzuführen.
quelle
In Design und Evolution von C ++ gibt Stroustrup einen weiteren Grund an ...
Dieselbe Header-Datei kann zwei oder mehr Implementierungsdateien enthalten, die von mehr als einem Programmierer gleichzeitig bearbeitet werden können, ohne dass ein Versionsverwaltungssystem erforderlich ist.
Dies mag heutzutage seltsam erscheinen, aber ich denke, es war ein wichtiges Problem, als C ++ erfunden wurde.
quelle
#ifdef
-fu zu missbrauchen . (Verwenden Sie einfach ein Makefile, das das richtigemyclass.o
aus dem plattenformabhängigenmyclass-win.cpp
/ generiertmyclass-linux.cpp
, und die Außenseite kümmert sich nur darummyclass.h
)Wenn Sie C ++ ohne Header-Dateien wollen, dann habe ich gute Nachrichten für Sie.
Es existiert bereits und heißt D ( http://www.digitalmars.com/d/index.html )
Technisch gesehen scheint D viel besser zu sein als C ++, aber es ist momentan nicht Mainstream genug für die Verwendung in vielen Anwendungen.
quelle
Eines der Ziele von C ++ ist es, eine Obermenge von C zu sein, und es ist schwierig, dies zu tun, wenn es keine Header-Dateien unterstützen kann. Wenn Sie Header-Dateien herausschneiden möchten, können Sie auch CPP (den Vorprozessor, nicht Plus-Plus) ganz herausschneiden. Sowohl C # als auch Java spezifizieren keine Makro-Vorprozessoren mit ihren Standards (es sollte jedoch beachtet werden, dass sie in einigen Fällen auch mit diesen Sprachen verwendet werden können und sogar verwendet werden).
Da C ++ gerade entwickelt wird, benötigen Sie - genau wie in C - Prototypen, um kompilierten Code, der auf externe Funktionen und Klassen verweist, statisch zu überprüfen. Ohne Header-Dateien müssten Sie diese Klassendefinitionen und Funktionsdeklarationen eingeben, bevor Sie sie verwenden können. Damit C ++ keine Header-Dateien verwendet, müssen Sie eine Funktion in der Sprache hinzufügen, die etwa das Java-
import
Schlüsselwort unterstützt. Das wäre eine wichtige Ergänzung und Veränderung. Um Ihre Frage zu beantworten, ob es praktisch wäre: Ich denke nicht - überhaupt nicht.quelle
Viele Menschen sind sich der Mängel von Header-Dateien bewusst, und es gibt Ideen, ein leistungsfähigeres Modulsystem in C ++ einzuführen. Vielleicht möchten Sie einen Blick auf Module in C ++ (Revision 5) von Daveed Vandevoorde werfen.
quelle
Nun, C ++ an sich sollte Header-Dateien aus Gründen der Abwärtskompatibilität nicht entfernen. Ich denke jedoch, dass sie im Allgemeinen eine dumme Idee sind. Wenn Sie eine Closed-Source-Bibliothek verteilen möchten, können diese Informationen automatisch extrahiert werden. Wenn Sie verstehen möchten, wie eine Klasse ohne Blick auf die Implementierung verwendet wird, sind Dokumentationsgeneratoren genau das Richtige für sie, und sie leisten verdammt viel bessere Arbeit.
quelle
Es ist sinnvoll, die Klassenschnittstelle in einer separaten Komponente zur Implementierungsdatei zu definieren.
Dies kann mit Schnittstellen geschehen, aber wenn Sie diesen Weg gehen, sagen Sie implizit, dass Klassen hinsichtlich der Trennung von Implementierung und Vertrag mangelhaft sind.
Modul 2 hatte die richtige Idee, Definitionsmodule und Implementierungsmodule. http://www.modula2.org/reference/modules.php
Die Antwort von Java / C # ist eine implizite Implementierung derselben (wenn auch objektorientiert).
Header-Dateien sind ein Problem, da Header-Dateien Implementierungsdetails ausdrücken (z. B. private Variablen).
Bei der Umstellung auf Java und C # stelle ich fest, dass, wenn eine Sprache IDE-Unterstützung für die Entwicklung erfordert (z. B. dass öffentliche Klassenschnittstellen in Klassenbrowsern navigierbar sind), dies möglicherweise eine Aussage ist, dass der Code nicht für sich allein steht besonders lesbar sein.
Ich finde die Mischung aus Schnittstelle und Implementierungsdetails ziemlich schrecklich.
Entscheidend ist, dass die fehlende Fähigkeit, die Signatur der öffentlichen Klasse unabhängig von der Implementierung in einer präzisen, gut kommentierten Datei zu dokumentieren, darauf hinweist, dass das Sprachdesign aus Gründen der Autorenschaft und der Wartung geschrieben wurde. Nun, ich streife jetzt über Java und C #.
quelle
Wenn Sie den Grund wollen, warum dies niemals passieren wird: Es würde so ziemlich die gesamte vorhandene C ++ - Software beschädigen. Wenn Sie sich einige der C ++ - Komiteedesign-Dokumentationen ansehen, haben sie verschiedene Alternativen untersucht, um zu sehen, wie viel Code es brechen würde.
Es wäre viel einfacher, die switch-Anweisung in etwas halbwegs Intelligentes zu ändern. Das würde nur einen kleinen Code brechen. Es wird immer noch nicht passieren.
BEARBEITET FÜR NEUE IDEE:
Der Unterschied zwischen C ++ und Java, der C ++ - Headerdateien erforderlich macht, besteht darin, dass C ++ - Objekte nicht unbedingt Zeiger sind. In Java werden alle Klasseninstanzen mit einem Zeiger referenziert, obwohl dies nicht so aussieht. In C ++ sind Objekte auf dem Heap und dem Stapel zugeordnet. Dies bedeutet, dass C ++ wissen muss, wie groß ein Objekt sein wird und wo sich die Datenelemente im Speicher befinden.
quelle
Ein Vorteil dieser Trennung besteht darin, dass nur die Benutzeroberfläche angezeigt werden kann, ohne dass ein erweiterter Editor erforderlich ist .
quelle
Ohne Header-Dateien existiert keine Sprache. Es ist ein Mythos.
Schauen Sie sich eine proprietäre Bibliotheksdistribution für Java an (ich habe keine nennenswerte C # -Erfahrung, aber ich würde erwarten, dass es dieselbe ist). Sie geben Ihnen nicht die vollständige Quelldatei; Sie geben Ihnen nur eine Datei, in der die Implementierung jeder Methode ausgeblendet ist (
{}
oder{return null;}
ähnliches) und alles, was sie mit verstecktem Versteck davonkommen können. Sie können das nur als Header bezeichnen.Es gibt jedoch keinen technischen Grund, warum ein C- oder C ++ - Compiler alles in einer entsprechend gekennzeichneten Datei zählen könnte, es
extern
sei denn, diese Datei wird direkt kompiliert. Die Kosten für die Kompilierung wären jedoch immens, da weder C noch C ++ schnell analysiert werden können, und das ist eine sehr wichtige Überlegung. Bei jeder komplexeren Methode zum Verschmelzen von Headern und Quellen treten schnell technische Probleme auf, z. B. die Notwendigkeit, dass der Compiler das Layout eines Objekts kennt.quelle
Header-Dateien sind ein wesentlicher Bestandteil der Sprache. Ohne Header-Dateien werden alle statischen Bibliotheken, dynamischen Bibliotheken und so ziemlich jede vorkompilierte Bibliothek unbrauchbar. Header-Dateien erleichtern auch die Dokumentation aller Elemente und ermöglichen das Durchsuchen der API einer Bibliothek / Datei, ohne jedes einzelne Codebit zu durchlaufen.
Sie erleichtern auch die Organisation Ihres Programms. Ja, Sie müssen ständig von der Quelle zum Header wechseln, aber Sie können damit auch interne und private APIs innerhalb der Implementierungen definieren. Zum Beispiel:
MySource.h:
extern int my_library_entry_point(int api_to_use, ...);
MySource.c:
int private_function_that_CANNOT_be_public(); int my_library_entry_point(int api_to_use, ...){ // [...] Do stuff } int private_function_that_CANNOT_be_public() { }
Wenn Sie
#include <MySource.h>
, dann bekommen Siemy_library_entry_point
.Wenn Sie
#include <MySource.c>
, dann sind Sie auch bekommenprivate_function_that_CANNOT_be_public
.Sie sehen, wie schlecht das sein kann, wenn Sie eine Funktion zum Abrufen einer Liste von Kennwörtern oder eine Funktion zum Implementieren Ihres Verschlüsselungsalgorithmus oder eine Funktion zum Freilegen der Interna eines Betriebssystems oder eine Funktion zum Überschreiben von Berechtigungen haben. etc.
quelle
Oh ja!
Nach dem Codieren in Java und C # ist es wirklich ärgerlich, 2 Dateien für jede Klasse zu haben. Also habe ich mir überlegt, wie ich sie zusammenführen kann, ohne den vorhandenen Code zu beschädigen.
In der Tat ist es wirklich einfach. Fügen Sie einfach die Definition (Implementierung) in einen Abschnitt #ifdef ein und fügen Sie in der Compiler-Befehlszeile eine Definition hinzu, um diese Datei zu kompilieren. Das ist es.
Hier ist ein Beispiel:
/* File ClassA.cpp */ #ifndef _ClassA_ #define _ClassA_ #include "ClassB.cpp" #include "InterfaceC.cpp" class ClassA : public InterfaceC { public: ClassA(void); virtual ~ClassA(void); virtual void methodC(); private: ClassB b; }; #endif #ifdef compiling_ClassA ClassA::ClassA(void) { } ClassA::~ClassA(void) { } void ClassA::methodC() { } #endif
Kompilieren Sie diese Datei in der Befehlszeile mit
Die anderen Dateien, die ClassA enthalten müssen, können dies nur
#include "ClassA.cpp"
Natürlich kann das Hinzufügen der Definition in der Befehlszeile einfach mit einer Makroerweiterung (Visual Studio-Compiler) oder mit automatischen Variablen (gnu make) und unter Verwendung derselben Nomenklatur für den Definitionsnamen hinzugefügt werden.
quelle
Trotzdem verstehe ich einige Aussagen nicht. Die Trennung von API und Implementierung ist eine sehr gute Sache, aber Header-Dateien sind keine API. Dort gibt es private Felder. Wenn Sie ein privates Feld hinzufügen oder entfernen, ändern Sie die Implementierung und nicht die API.
quelle