Frage

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

function someFunc();

function someFunc()
{
  //Implementation.
}

In der Tat, in der Definition von Klassen, ist dies oft der Fall ist. Eine Klasse wird in der Regel mit ihren Mitgliedern in einer .h-Datei deklariert, und diese werden dann in einer entsprechenden .C-Datei definiert.

Was sind die Vorteile und Nachteile dieses Ansatzes?

War es hilfreich?

Lösung

Historisch war dies der Compiler zu helfen. Sie haben es die Liste der Namen geben, bevor er sie verwendet -., Ob dies die tatsächliche Nutzung war, oder eine Vorwärtsdeklaration (C-Standard funcion Prototyp beiseite)

Moderne Compiler für moderne Sprachen zeigen, dass dies nicht mehr eine Notwendigkeit, so C & C ++ 's (sowie Objective-C, und wahrscheinlich noch andere) Syntax hier ist histotical Gepäck. In der Tat ist man dies eines der großen Probleme mit C ++, dass auch die Zugabe eines geeigneten Modulsystem nicht lösen.

Nachteile sind: viele stark Include-Dateien verschachtelt (I verfolgt habe sind Bäume vor, sie überraschend groß sind) und Redundanz zwischen Erklärung und Definition - alles was zu mal länger Codierung und längeren Zeiten kompiliert (je die Kompilierung Zeiten im Vergleich zwischen vergleichbar C ++ und C # -Projekten? Dies ist einer der Gründe für den Unterschied). Header-Dateien müssen für die Nutzer von Komponenten zur Verfügung gestellt werden Sie zur Verfügung stellen. Die Chancen von ODR Verletzungen. Das Vertrauen auf die Pre-Prozessor (viele moderne Sprachen keine Pre-Prozessor Schritt müssen), die Ihren Code zerbrechlicher und härter für Werkzeuge macht zu analysieren.

Vorteile: keine viel. Man könnte argumentieren, dass Sie zusammen zu Dokumentationszwecken an einem Ort gruppiert, um eine Liste von Funktionsnamen bekommen - aber die meisten IDEs haben eine Art von Code in diesen Tagen Faltbarkeit und Projekte jeder Größe sollten mit doc-Generatoren (wie doxygen) sowieso. Mit einem Reiniger, Pre-Prozessor-less, Modul basierter Syntax ist es einfacher für die Tools, um Ihren Code zu folgen und bieten diese und mehr, so dass ich denke, das ist „Vorteil“ ist nur etwa strittig.

Andere Tipps

Es ist ein Artefakt, wie C / C ++ Compiler arbeiten.

Als Quelldatei kompiliert wird, ersetzt der Präprozessor jeweils # include-Anweisung mit dem Inhalt der Datei enthält. Erst danach wird versucht der Compiler das Ergebnis dieser Verkettung zu interpretieren.

Der Compiler geht dann über dieses Ergebnis vom Anfang bis zum Ende und versucht, jede Aussage zu bestätigen. Wenn eine Codezeile eine Funktion aufruft, die nicht vorher festgelegt hat, wird es geben.

Es gibt ein Problem mit, dass, obwohl, wenn es um für beide Seiten rekursive Funktion kommt aufruft:

void foo()
{
  bar();
}

void bar()
{
  foo();
}

Hier foo nicht als bar kompilieren, ist unbekannt. Wenn Sie die beiden Funktionen rund um Schalter, bar nicht als foo kompilieren ist nicht bekannt.

Wenn Sie Deklaration und Definition trennen, obwohl, können Sie die Funktionen bestellen, wie Sie wollen:

void foo();
void bar();

void foo()
{
  bar();
}

void bar()
{
  foo();
}

Wenn hier der Compiler Prozesse foo es bereits die Signatur einer Funktion namens bar weiß, und ist glücklich.

Natürlich Compiler in einer anderen Art und Weise funktionieren konnte, aber das ist, wie sie in C, C ++ arbeiten und bis zu einem gewissen Grad Objective-C.

Nachteile:

Keine direkt. Wenn Sie mit C / C ++ wie auch immer, es ist der beste Weg, Dinge zu tun. Wenn Sie die Wahl der Sprache / Compiler haben, dann vielleicht können Sie eine auswählen, wo dies kein Problem ist. Das einzige, was mit Splitting Erklärungen in Header-Dateien zu prüfen, ist für beide Seiten rekursive # zu vermeiden include-Anweisungen -. Aber das ist, was sind Wachen sind für

Vorteile:

  • Compilation. Geschwindigkeit: Da alle darin enthaltenen Dateien verkettet werden und dann analysiert, um die Menge und Komplexität von Code in enthaltenen Dateien zu reduzieren wird Kompilierung verbessern
  • Vermeiden Sie Code-Duplizierung / inlining: Wenn Sie vollständig eine Funktion in einer Header-Datei definieren, jedes Objekt-Datei, die diesen Header und Referenzen dieser Funktion seine eigene Version dieser Funktion enthält enthält. Als Randnotiz, wenn Sie wollen inlining, müssen Sie die vollständige Definition in der Header-Datei setzen (auf den meisten Compiler).
  • Encapsulation / Klarheit: Eine gut definierte Klasse / Gruppe von Funktionen sowie einige Dokumentation sollte genug sein für andere Entwickler den Code zu verwenden. Es gibt (im Idealfall) keine Notwendigkeit für sie zu verstehen, wie der Code funktioniert - warum also verlangen, daß sie durch sie sichten? (Das Gegenargument, dass es nützlich sein kann für sie die Umsetzung für den Zugriff auf bei Bedarf steht nach wie vor, natürlich).

Und natürlich, wenn Sie eine Funktion überhaupt bei der Aufdeckung nicht interessiert sind, können Sie in der Regel immer noch wählen, um es vollständig in der Implementierungsdatei zu definieren, anstatt die Kopfzeile.

Es gibt zwei wesentliche Vorteile Trennung Erklärung und Definition in C ++ Header und Quelldateien. Die erste ist, dass Sie Probleme zu vermeiden, die mit der One Definition Rule wenn Ihre Klasse / Funktionen / was auch immer sind #included in mehr als einem Ort. Zweitens, indem die Dinge so zu tun, trennen Sie Schnittstelle und Implementierung. Benutzer Ihrer Klasse oder Bibliothek benötigen nur Ihre Header-Datei, um zu sehen, Code zu schreiben, der es verwendet. Sie kann auch noch einen Schritt weiter mit dem Pimpl Idiom und machen es so, dass Benutzer Code muss nicht jedes Mal die Bibliothek Implementierung Änderungen neu kompilieren.

Sie haben bereits erwähnt, den Nachteil der Codewiederholung zwischen den .h und CPP-Dateien. Vielleicht habe ich zu lange C ++ Code geschrieben, aber ich glaube nicht, dass es , die schlecht. Sie müssen alle jedes Mal, Anwendercode ändern Sie eine Funktion Signatur sowieso ändern, so was ist eine weitere Datei? Es ist nur ärgerlich, wenn Sie zuerst eine Klasse zu schreiben, und Sie haben kopieren und einfügen aus dem Header in die neuen Quelldatei.

Der andere Nachteil in der Praxis ist, dass, um (und zu debuggen!) Guter Code zu schreiben, die eine Drittanbieter-Bibliothek verwendet, müssen Sie in der Regel im Innern sehen. Das bedeutet, Zugriff auf den Quellcode, auch wenn Sie es 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 deine Schuld oder ihre. Auch an der Quelle suchen gibt Ihnen Aufschluss darüber, wie man richtig nutzen und eine Bibliothek erweitern, dass die Dokumentation möglicherweise nicht decken. Nicht jedes Schiff ein MSDN mit ihrer Bibliothek. Und große Software-Ingenieure haben eine böse Gewohnheit Dinge mit Ihrem Code zu tun, die Sie nie für möglich gehalten haben. ; -)

Der Standard verlangt, dass bei der Verwendung einer Funktion, eine Erklärung in ihrem Umfang sein muss. Dies bedeutet, dass der Compiler in der Lage sein sollte, gegen einen Prototyp (die Erklärung in einer Header-Datei), um zu überprüfen, was Sie es sind vorbei. Außer natürlich, für Funktionen, die variadische sind - solche Funktionen nicht Argumente bestätigen

.

Denken Sie an C, wenn dies nicht erforderlich war. Zu diesem Zeitpunkt behandelt Compiler keine Angabe Rückgabetyp vorbelegt werden in int. Nun sei angenommen, Sie eine Funktion foo () hatten, die einen Zeiger zurück für ungültig zu erklären. da Sie keine Erklärung jedoch haben, wird denken, der Compiler, dass es eine ganze Zahl zurückgeben muss. Bei einigen Motorola-Systemen zum Beispiel würden integere und Zeiger in verschiedenen Registern zurückgeschickt werden. Nun wird der Compiler nicht mehr das richtige Register verwenden und stattdessen zurückgeben Ihre Zeiger gegossen auf eine ganze Zahl in dem anderen Register. In dem Moment, Sie versuchen, mit diesem Zeiger zu arbeiten - bricht die Hölle los

.

Deklarieren von Funktionen innerhalb des Headers ist in Ordnung. Aber denken Sie daran, wenn Sie in der Kopfzeile deklariert und definiert sicherstellen, dass sie inline sind. Eine Möglichkeit, dies zu erreichen, ist die Definition innerhalb der Klassendefinition zu setzen. prepend sonst das inline Schlüsselwort. Sie werden sonst in ODR Verletzung ausgeführt wird, wenn der Kopf in mehreren Implementierungsdateien enthalten ist.

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

Die Deklaration, in dem Sie den Namen angeben, die Parameter und die Mitglieder (im Falle einer Struktur / Klasse) und die Definition in dem Sie festlegen, was die Funktionen der Fall ist.

Zu den Nachteilen sind Wiederholung, doch ein großer Vorteil ist, dass Sie Ihre Funktion als int foo(float f) erklären können und lassen Sie die Details bei der Umsetzung (= Definition), so dass jeder, der will, Ihre Funktion foo verwenden enthält nur Ihre Header-Datei und Links Ihre Bibliothek / object, so Bibliotheksbenutzer sowie Compiler nur für die definierte Schnittstelle zu sorgen haben, die die Schnittstellen und Geschwindigkeiten kompilieren Zeiten bis zu verstehen hilft.

Ein Vorteil, den ich noch nicht gesehen haben: API

Jede Bibliothek oder 3rd-Party-Code, der nicht Open Source (das heißt proprietär) ist ihre Umsetzung nicht zusammen mit der Verteilung hat. Die meisten Unternehmen sind einfach nur nicht bequem mit Quellcode verlosen. Die einfache Lösung, verteilt nur die Klassendeklarationen und Funktionssignaturen, die von der DLL ermöglichen.

Disclaimer: Ich bin mir nicht sagen, ob es richtig, falsch ist, oder gerechtfertigt, ich sage nur, ich habe es viel gesehen

.

Vorteil:

Klassen können von anderen Dateien referenziert werden, indem nur die Erklärung enthält. Definitionen können dann später in dem Übersetzungsvorgang verknüpft werden.

Ein großer Vorteil von Vorwärts-Erklärungen ist, dass bei der Verwendung sorgfältig können Sie die Kompilierung Abhängigkeiten zwischen den Modulen abgeholzt.

Wenn ClassA.h auf ein Datenelement beziehen muss in ClassB.h, können Sie verwenden oft nur eine Vorwärts-Referenzen in ClassA.h und umfassen ClassB.h in ClassA.cc statt in ClassA.h, so Abholzen eine Zeitabhängigkeit kompilieren.

Für große Systeme dies eine große Zeitersparnis auf einem Build sein kann.

  1. Die Trennung sorgt für eine saubere, klare Sicht von Programmelementen.
  2. Möglichkeit zum Erstellen und Link zu Binärmodule / Bibliotheken ohne Quellen offen zu legen.
  3. Link-Binärdateien ohne Quellen neu zu kompilieren.

Wenn es richtig gemacht, diese Trennung verringert mal kompilieren, wenn nur die Implementierung geändert hat.

Nachteil

Das führt zu viel Wiederholung. Der größte Teil der Funktionssignatur muss in zwei oder mehr gesetzt werden (wie Paulious angegeben) Orte.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top