Möglichkeiten, die Oberfläche und Implementierung in C ++ zu organisieren


12

Ich habe gesehen, dass es in C ++ verschiedene Paradigmen gibt, was in die Header-Datei und was in die CPP-Datei geht. AFAIK, die meisten Leute, besonders die mit C-Hintergrund, machen:

foo.h

 class foo {
 private:
     int mem;
     int bar();
 public:
     foo();
     foo(const foo&);
     foo& operator=(foo);
     ~foo();
 }

foo.cpp

 #include foo.h
 foo::bar() { return mem; }
 foo::foo() { mem = 42; }
 foo::foo(const foo& f) { mem = f.mem; }
 foo::operator=(foo f) { mem = f.mem; }
 foo::~foo() {}
 int main(int argc, char *argv[]) { foo f; }

In der Regel unterrichten meine Dozenten Anfängern wie folgt C ++:

foo.h

 class foo {
 private:
     int mem;
     int bar() { return mem; }
 public:
     foo() { mem = 42; }
     foo(const foo& f) { mem = f.mem; }
     foo& operator=(foo f) { mem = f.mem; }
     ~foo() {}
 }

foo.cpp

 #include foo.h
 int main(int argc, char* argv[]) { foo f; }
 // other global helper functions, DLL exports, and whatnot

Ursprünglich aus Java stammend, habe ich mich aus mehreren Gründen immer an diesen zweiten Weg gehalten, zum Beispiel, dass ich nur an einer Stelle etwas ändern muss, wenn sich die Schnittstellen- oder Methodennamen ändern, dass ich die unterschiedlichen Einrückungen von Dingen in Klassen mag, wenn ich Blick auf ihre Umsetzung, und das finde ich Namen besser lesbar als im fooVergleich zu foo::foo.

Ich möchte Vor- und Nachteile für beide Arten sammeln. Vielleicht gibt es noch andere Möglichkeiten?

Ein Nachteil meines Weges ist natürlich die Notwendigkeit gelegentlicher Vorwärtserklärungen.


2
foo.cppJetzt hat nichts mit deiner fooKlasse zu tun und sollte leer gelassen werden (vielleicht aber #includeum dein Build-System glücklich zu machen).
Benjamin Bannier

2
Ihre Dozenten sind verrückt.
Leichtigkeit Rennen mit Monica

Antworten:


16

Während die zweite Version einfacher zu schreiben ist, vermischt sie Schnittstelle mit Implementierung.

Quelldateien, die Headerdateien enthalten, müssen bei jeder Änderung der Headerdateien neu kompiliert werden. In der ersten Version würden Sie die Header-Datei nur ändern, wenn Sie die Benutzeroberfläche ändern müssen. In der zweiten Version würden Sie die Header-Datei ändern, wenn Sie die Schnittstelle oder die Implementierung ändern müssen.

Außerdem sollten Sie die Implementierungsdetails nicht offenlegen , da Sie mit der zweiten Version keine unnötige Neukompilierung erhalten .


1
+1 Mein Profiler instrumentiert den Code in den Header-Dateien nicht - das ist auch ein wertvoller Grund.
Eugene

Wenn Sie meine Antwort auf diese Frage sehen , werden Sie sehen, wie sehr dies von der Semantik der Klasse abhängt, dh von der Verwendung (insbesondere, wenn es sich um einen exponierten Teil von handelt) Ihre Benutzeroberfläche und wie viele andere Klassen in Ihrem System verwenden sie direkt).
CashCow

3

Ich habe es den zweiten Weg zurück in '93 -95 gemacht. Es dauerte ein paar Minuten, um eine kleine App mit 5-10 Funktionen / Dateien neu zu kompilieren (auf demselben 486 PC ... und nein, ich wusste auch nichts über Klassen, ich war erst 14-15 Jahre alt und es gab kein Internet ) .

Was Sie Anfängern beibringen und was Sie beruflich anwenden, sind also ganz andere Techniken, insbesondere in C ++.

Ich denke, der Vergleich zwischen C ++ und einem F1-Auto ist passend. Sie setzen keine Anfänger in ein F1-Auto (das nicht einmal anspringt, wenn Sie den Motor nicht auf 80-95 Grad Celsius vorheizen).

Unterrichten Sie C ++ nicht als erste Sprache. Sie sollten genug Erfahrung haben, um zu wissen, warum Option 2 schlechter ist als Option 1 im Allgemeinen, ein bisschen zu wissen, was statisches Kompilieren / Verknüpfen bedeutet, und um zu verstehen, warum C ++ es auf die erste Art bevorzugt.


Diese Antwort wäre noch besser, wenn Sie ein wenig über statische Kompilierung / Verknüpfung (ich wusste es damals nicht!)
Felix Dombek

2

Die zweite Methode würde ich eine vollständig inline Klasse nennen. Sie schreiben eine Klassendefinition, aber der gesamte Code, der diese verwendet, fügt den Code nur ein.

Ja, der Compiler entscheidet, wann und wann nicht ... In diesem Fall helfen Sie dem Compiler, eine Entscheidung zu treffen, und Sie werden möglicherweise weniger Code generieren und möglicherweise sogar schneller.

Dieser Vorteil wird wahrscheinlich die Tatsache aufwiegen, dass Sie, wenn Sie die Implementierung einer Funktion ändern, die gesamte Quelle, die sie verwendet, neu erstellen müssen. Aufgrund der Leichtigkeit der Klasse werden Sie die Implementierung nicht ändern. Wenn Sie eine neue Methode hinzufügen, müssten Sie den Header trotzdem ändern.

Wenn Ihre Klasse jedoch komplexer wird und sogar eine Schleife hinzufügt, verringert sich der Vorteil dieser Vorgehensweise.

Es hat immer noch seine Vorteile, insbesondere:

  • Wenn es sich um Code mit allgemeiner Funktionalität handelt, können Sie den Header einfach einschließen und aus mehreren Projekten verwenden, ohne eine Verknüpfung mit einer Bibliothek herstellen zu müssen, die den Quellcode enthält.

Die Kehrseite des Inlinings wird zu einem Problem, wenn Sie Implementierungsspezifikationen in Ihre Kopfzeile einfügen müssen, dh wenn Sie zusätzliche Kopfzeilen einfügen müssen.

Beachten Sie, dass Vorlagen ein Sonderfall sind, da Sie die Implementierungsdetails ziemlich genau angeben müssen. Sie könnten es in einer anderen Datei verdecken, aber es muss dort sein. (Es gibt eine Ausnahme von dieser Regel bei Instanziierungen, aber im Allgemeinen fügen Sie Ihre Vorlagen ein).


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.