3 Stimmen

C++ Was in einer Klassendeklaration stehen soll und was nicht

Ich bin völlig verwirrt, wo z.B. die Definition eines Konstruktors platziert werden soll. Manchmal sieht man so etwas wie das:

// point.h
class Point {
  Point(int x, int y) {
    x_ = x;
    y_ = y;
  }
private:
  const int x_;
  const int y_;
}

Dann sieht man manchmal etwas wie das:

// point.h
class Point {
  Point(int x, int y);
private:
  const int x_;
  const int y_;
}

// point.cc
Point::Point(int x, int y) {
  x_ = x;
  y_ = y;
}

Also manchmal werden Dinge wie Konstruktoren, Kopierkonstruktoren usw. in der .h-Datei deklariert und dann in der .cc-Datei implementiert, manchmal werden sie im Header definiert usw. Aber unter welchen Umständen? Was ist eine gute Praxis, was nicht?

5voto

phlipsy Punkte 2761

Im Gegensatz zu einigen der Antworten gibt es Unterschiede zwischen diesen beiden Praktiken.

Wenn Sie die Implementierung in die Klassendeklaration einfügen, wird die Methode automatisch als inline markiert. Für sehr kurze Methoden wie Wrapper ist dies eine empfohlene Praxis sehr nützliches Feature.

Wenn Sie die Implementierung in eine separate .cc Datei einfügen, erhöhen Sie die Lesbarkeit (wie David Titarenco bereits in seiner Antwort schrieb) und verringern mögliche Abhängigkeiten in Ihrem Projekt. Klassen, die nur als Referenzen, Zeiger oder Rückgabewerte in der Methodendeklaration vorkommen, benötigen nur eine Vorwärtsdeklaration im Header. Ihre Implementierungsdetails sind hier irrelevant und somit auch für jede Datei, die diesen Header inkludiert. Ein Beispiel:

// test.h
class A; // Vorwärtsdeklaration von A
// #include "decl_of_class_A.h" // hier nicht benötigt, weil ...

class B {
public:
  A method1();        // in diesen Fällen ist die Vorwärtsdeklaration
  void method2(A* a); // von A oben komplett ausreichend
  void method3(A& a);
};

// test.cc
#include "decl_of_class_A.h" // hier benötigen wir wirklich
                             // die komplette Deklaration von A
                             // um es *nutzen* zu können

A B::method1() { /*...*/ }
// ...

Wenn Sie nun test.h in eine andere .cc Datei einbinden und sich die Deklaration der Klasse A ändert, muss nur test.cc neu kompiliert werden und nicht die andere .cc Datei. Die Deklaration der Klasse B hat sich nicht geändert.

Davon abgesehen: Es gibt Situationen, in denen Sie verpflichtet sind, die Implementierungen in Ihrem Header zu platzieren: Wenn Sie Klassen- oder Funktionstemplates schreiben oder wirklich möchten, dass eine Funktion als inline deklariert wird.

Wie Omnifarious genau kommentiert hat: Der angehängte Code, den Sie manchmal gesehen haben, wird von jedem C++ Compiler sicherlich abgelehnt. const Elemente einer Klasse müssen in einer Initialisierungsliste initialisiert werden. Danach bleiben sie festgelegt und es wird kein Standardzuweisungsoperator bereitgestellt. Das bedeutet, selbst wenn Sie eine Initialisierungsliste verwenden, werden Sie nie in der Lage sein, so etwas zu schreiben, solange Sie den Zuweisungsoperator nicht überladen haben:

Point p(10, 20);
Point q(0, 0);
q = p; // kein Zuweisungsoperator vorhanden ==> Fehler!

Wenn Sie wirklich konstante Koordinaten benötigen, sollten Sie const Point p(10, 20) schreiben.

2voto

David Titarenco Punkte 31667

Es ist in der Regel besser, Deklarationen im Kopf und Implementierungen im Quelltext zu halten. Mit diesem gesagt, liegt es ganz beim Autor/den Autoren.

Solange es lesbar ist :)

1voto

Dan Punkte 3316

Wenn Sie Header-Dateien verwenden, ist es in der Regel ratsam, die Methoden nur im Header zu definieren und sie im cpp zu implementieren.

Sie können Klassenmethoden (und Konstruktoren/Destruktoren) innerhalb oder außerhalb der Klassendefinition definieren. Beides ist kein Problem, es ist nur eine Frage des Stils.

Der Unterschied liegt nur in der Syntax, denn wenn Sie eine Methode außerhalb einer Klasse definieren, müssen Sie TheClassName:: direkt vor dem Namen der Methode hinzufügen (aber nach dem Typ).

1voto

EboMike Punkte 74805

Sie sollten alle Funktionsimplementierungen außerhalb der Headerdatei behalten, es sei denn,

  • Sie müssen es tun, weil sie Vorlagen sind
  • Sie möchten, dass sie inline sind und Ihr Compiler kein LTCG durchführt.

Eine saubere Headerdatei ist unschätzbar, um einen schnellen Überblick darüber zu erhalten, was eine Klasse macht und wie Sie sie verwenden sollen.

1voto

Omnifarious Punkte 52299

Natürlich sind beide Definitionen nicht der beste Weg. Das ist jedoch nur am Rande relevant für deine Frage.

Die Antwort ist, dass du die erste Definition verwenden solltest, wenn die Methode sehr kurz ist und die Klassendefinition nicht überladen wird. Die erste Definition innerhalb der Klasse hat den Effekt, dass implizit ein inline Schlüsselwort vor der Definition platziert wird.

Die zweite Definition kann auch in einer Headerdatei deklariert werden, und du kannst ein explizites inline Schlüsselwort verwenden. Dies ist häufiger der Fall, wenn die Methode komplexer ist oder Teil einer Template-Spezifikation ist, bei der die vollständige Definition verfügbar sein muss, damit das Template ordnungsgemäß mit aktuellen Compilern instanziiert werden kann.

Also, hauptsächlich handelt es sich um eine Stilfrage, welche Art der Definition du verwendest. Wenn du jedoch die zweite Definition, die externe Deklarationsart der Klasse, verwendest und deine Methode in einer Headerdatei deklariert ist, sollte deine Methode explizit inline deklariert werden oder Mitglied einer Template-Klasse sein. Andernfalls könnte der Linker Warnungen vor doppelter Definition ausgeben.

Zuletzt, hier ist wie die Methode wirklich definiert werden sollte:

class Point {
  Point(int x, int y) : x_(x), y_(y) { }
private:
  const int x_;
  const int y_;
};

Dies liegt daran, dass die Member const sind. Sie müssen innerhalb der Konstruktor-Initialisierungsliste initialisiert werden, nicht im Konstruktor-Körper. In der Konstruktor-Body sind diese Zuweisungen illegal, weil die Member const sind.

Es ist jedoch eine gute Angewohnheit, immer Initialisierungslisten zu verwenden, um die Member zu initialisieren, unabhängig davon, ob sie const sind oder nicht. Dies liegt daran, dass wenn du Zuweisungen im Konstruktor-Körper verwendest, du den Standardkonstruktor für das Member aufrufst und dann etwas zuweisen musst. Dies ist wahrscheinlich aufwändiger, als es einfach mit dem richtigen Wert zu initialisieren, besonders wenn der Typ kein primitiver Typ ist.

Es hilft auch Fehler zu vermeiden, da deine Member immer initialisiert werden, anstatt dass ihre Initialisierung vom Kontrollfluss im Konstruktor-Körper abhängt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X