Gutes Klassendesign (und warum Zeiger im Konstruktor?)
-
Hallo liebe Community,
ich lerne momentan C++ mit dem Buch "C++ lernen und professionell anwenden". Zuvor hatte ich schon mal etwas Java mit der Java Insel gelernt, jetzt verwirren mich ein paar Sachen beim Thema Klassen.
z . B. wird hier (s. 286) der Konstruktor der Klasse Konto definiert
Konto::Konto(const string& k_name, unsigned long k_nr, double k_stand) { nr = k_nr; name = k_name; stand = k_stand; }1. In dem Buch werden sehr oft im Konstruktor-Parameter Zeiger benutzt. Das kenne ich so aus Java nicht. Da ich noch nicht so bewandert mit Zeigern bin, frage ich mich ob das zum "guten Klassendesign" gehört und ob man das generell so macht - und was das überhaupt bringen soll...
2. In Java habe ich ich mir immer für die Zuweisung der privaten Variablen (in dem Fall hier nr, name, stand) Setter-Methoden geschrieben welche ich auch im Konstruktor benutzt habe. Das sehe ich hier in dem C++ Buch nicht.
Also z. b
setName(k_name);Macht man das in C++ normalerweise auch so?
___________________________________________________In dem Programmbeispiel habe ich z. B. einen Getter gefunden:
class Konto { private: string name; unsigned long nr; double stand; public: Konto (const string&, unsigned long, double); Konto (const string& ); bool init (const string&, unsigned long, double); void display(); };void display() (zeigt die Infos von name, stand und nr an )... In Java hätte ich die Methode in jedem Falle so genannt, dass ersichtlich ist das es sich um einen Getter handelt. Also z. B.
void getInfos();______________________________________________________________
In den beiden Progammbeispielen ist die Klasse selbst in einer Headerdatei (konto.h) definiert, die Konstruktoren in einer Quelldatei (konto.cpp).
Macht man das in C++ so - und wenn ja: Warum? In Java habe ich das alles in eine Datei geschrieben... Wäre doch auch übersichtlicher?
Muss ich in der konto.cpp die Headerdatei konto.h inkludieren? Oder weiß der Compiler das schon weil ich die Header-Datei so nenne wie die Klasse selbst?
_______________________________________________________________Kann mir jemand sagen ob es in C++ quasi eine Standard-Konvention für Klassendesigns gibt? Also wie so ein Klassendesign generell auszusehen hat?
Ich bedanke mich.
Viele Grüße
-
Ich weiß jetzt nicht, wo Du da Zeiger siehst. Aber ich denke mal, Du wunderst dich gerade, warum da "const string&" und nicht einfach "string" steht. Wenn du
void foo(string lokale_kopie) { lokale_kopie += "dings"; }schreibst, ist der Parameter eine lokale und veränderbare Kopie. Manchmal ist das Kopieren von Objekten teuer. Wenn Du also nur lesend auf etwas zugreifen willst, kannst Du das mit dem Kopieren unterbinden:
void foo(string const& string_referenz) // oder auch const string& { cout << string_referenz << endl; }Am Aufruf ändert sich dabei nichts. In Deinem Buch mal einfach zum Thema Referenzen nachgucken.
Seit letztem Jahr (C++2011) haben wir im Wesentlichen diese zwei Möglichkeiten, wenn es um einen nicht-trivialen Typen wie std::string geht:
class Person { string name; public: explicit Person(string n) // n ist eine lokale Kopie : name(move(n)) // aber eine Kopie brauchen wir sowieso {} // wir lassen sie in das Person-Objekt "umziehen" };Code, der typisch für C++2003 ist:
class Person { string name; public: explicit Person(string const& n) // n ist eine Referenz : name(n) // hier wird kopiert {} };Nachteil: für n wird ggf ein temporäres string-Objekt als Ergebnis einer Konvertierung erzeugt, welches dann nochmal unnötigerweise kopiert wird.
Weniger schlau ist:
class Person { string name; public: explicit Person(string n) : name(n) {} };da hier
neine lokale Kopie ist, die dann nochmal nachnamekopiert wird, obwohl n dann nicht mehr benötigt wird und sich wenig später wieder in Luft auflöst. Ohne C++2011, also ohne move, könntest Du das noch so optimieren:class Person { string name; public: explicit Person(string n) { name.swap(n); } };da sich string-Werte i.d.R. sehr schnell (in konstanter Zeit) austauschen lassen.
Also: Ein bissel aufpassen, dass Du nicht zu viele unnötige Kopien erzeugst. Bei einer nicht-trivialen Klasse wird sonst ein beliebig komplexer Kopierkonstruktor aufgerufen.
-
Die Funktionsweise leuchtet ein. Danke dafür
!Jemand noch Tipps bezüglich der anderen Fragen?
Viele Grüße
-
Im Konstruktor auch besser eine Initialisierungsliste verwenden:
Konto::Konto(const string& k_name, unsigned long k_nr, double k_stand) { nr = k_nr; name = k_name; stand = k_stand; }->
Konto::Konto(const string& k_name, unsigned long k_nr, double k_stand) : nr(k_nr), name(k_name), stand(k_stand) {} Konto::Konto() : nr(0), stand(0) {}
-
Hier Initialisierst du etwas:
setName(k_name);es ist das Gleiche wie:
std::string k_name = "lol";Wen du erst im Konstruktor die Variablen definierst ist es so:
Whatever::Whatever(std::string& k_name) { name = k_name; }es ist das Gleiche wie:
std::string name; name = "lol";
-
swapswap schrieb:
In dem Programmbeispiel habe ich z. B. einen Getter gefunden:
class Konto { private: string name; unsigned long nr; double stand; public: Konto (const string&, unsigned long, double); Konto (const string& ); bool init (const string&, unsigned long, double); void display(); };void display() (zeigt die Infos von name, stand und nr an )... In Java hätte ich die Methode in jedem Falle so genannt, dass ersichtlich ist das es sich um einen Getter handelt. Also z. B.
void getInfos();Wie kann eine Funktion die mit void beginnt und mit () aufhört ein getter sein?!
-
Danke für die vielen Antworten, das hilft mir wirklich extrem weiter.
Tippgeber schrieb:
Wie kann eine Funktion die mit void beginnt und mit () aufhört ein getter sein?!
Danke ! Logikfehler meinerseits. Ich mach ja nichts mehr mit den Daten
...
Schon etwas her als ich das mal in Java gelernt hatte.