Multidimensionale Menüstruktur mit OOP und stl::vector
-
Hallo, alle miteinander,
obwohl blutiger Anfänger in c++ (komme eher aus dem PHP-Umfeld) möchte ich auf möglichst einfache Weise multidimensionale, mehrsprachige Menüstrukturen mittels OOP, stl::vector und stl::map erstellen. Nachstehenden Code, der womöglich - so habe ich mir schon sagen lassen - die Gefahr von Speicherlecks in sich birgt - habe ich mit g++ lauffähig komplieren können - im Anschluss an das (auf das Notwendige beschränkte) Listing folgt eine Beschreibung des Programms bzw. meines derzeitigen Problems damit.
#include <stdio.h> #include <iostream> #include <cstdlib> #include <string> #include <map> #include <vector> using namespace std; class menueItemBase { public: int var1; string var2; map <string, string> titles; menueItemBase(); ~menueItemBase(){} void show_title(string lang) { cout << this->titles[lang].c_str() << endl; } void set_title(string lang, string title) { this->titles.insert(pair<string, string> (lang, title)); } }; menueItemBase::menueItemBase(){} class menueItem: public menueItemBase { public: vector<menueItemBase*>subitems; menueItem(); ~menueItem(){} void show_subitems(string lang, menueItem* obj) { for (unsigned i=0; i<obj->subitems.size(); i++) { obj->subitems[i]->show_title(lang); //obj->subitems[i]->show_subitems(lang, obj->subitems[i]); } }; }; menueItem::menueItem() {} int main() { menueItem *o1 = new menueItem; o1->set_title("en", "Main Menue"); o1->set_title("de", "Hauptmenü"); menueItem *o2 = new menueItem; o2->set_title("en", "Edit"); o2->set_title("de", "Bearbeiten"); menueItem *o3 = new menueItem; o3->set_title("en", "Preferences"); o3->set_title("de", "Einstellungen"); o2->subitems.push_back(o3); o1->subitems.push_back(o2); menueItem *o4 = new menueItem; o4->set_title("en", "View"); o4->set_title("de", "Ansicht"); cout << "--------------" << endl; o1->subitems.push_back(o4); o1->show_title("de"); cout << "--------------" << endl; o1->show_subitems("de", o1); cout << "--------------" << endl; o2->show_subitems("en", o2); cout << "--------------" << endl; }Die Ausgabe von main() lautet:
-------------- Hauptmenü -------------- Bearbeiten Ansicht -------------- Preferences --------------Kurze Beschreibung:
Damit der Vektor subitems, über den jede Instanz der Klasse menueItem verfügt, Objekte aufnehmen kann, muss er benutzerdefiniert (also auf einer Klasse basieren) - soweit richtig, oder?
Der Vektor wird von der Klasse menueItemBase bereitgestellt. Neben diesem erbt die Klasse menueItem von menueItemBase ein paar Eigenschaften und Methoden (in Wirklichkeit erheblich mehr, als hier dargestellt - wie auch immer.)
Jedenfalls ist ein Objekt der Klasse menueItem nun in der Lage, in diesem Vektor beliebig viele Objekte der eigenen Art zu enthalten. Darüber war ich zunächst schon mal sehr glücklich - aber nicht allzu lange, da ich diese Objekte auch wieder rekursiv auslesen will.
Kommentiere ich in der Funktion show_subitems zu diesem Zweck die Zeile, von der ich glaubte, dass sie evtl. dafür sorgen könnte, dass die Funktion alle Vektoren aller Unterobjekte ausließt ein, also...
obj->subitems[i]->show_subitems(lang, obj->subitems[i]);... wirft der Compiler folgenden Fehler
menueItem.cpp: In member function ‘void menueItem::show_subitems(std::string, menueItem*)’: menueItem.cpp:43: error: ‘class menueItemBase’ has no member named ‘show_subitems’Merkwürdig, weil in main() (zumindest nach meinem Dafürhalten) gar keine Objektinstanzen der Klasse menueItemBase, sondern der Klasse menueItem erzeugt wurden, die der sehr wohl eine member-Funktion namens show_subitems() zur Verfügung stellt. Komme einfach nicht dahinter, was da schief läuft.
Über jede weiterführende Hilfe würde ich mich sehr freuen
- vielen Dank im Voraus!!!
-
c+dummie schrieb:
stl::vector und stl::map
Der Namensraum heißt std, STL ist lediglich der Oberbegriff für die Standardbibliotheks Template Klassen und Funktionen.
Was du falsch machst, ist, dass du selber Speicher allokierst. Da ist keine Gefahr, sondern es ist schon von vornherein klar, dass du Speicherlecks erzeugst (wenn auch nicht besonders große). Tipp: Allokiere selbst so wenig Speicher wie möglich.
Aber wenn dus wirklich wissen willst:
Für jedesneweindelete. C++ ist anders, hier gibt es keine Garbage Collection.
-
c+dummie schrieb:
stl::vector und stl::mapHallo Hacker,
erst mal vielen Dank für die rasche Reaktion. Wegen dem Dreher stl vs. std möchte ich mich entschuldigen - bin wie gesagt weitgehend unerfahren mit C++ und der Lernaufwand schein ja beträchtlich zu sein, selbst wenn man sich z.B. mit OOP unter PHP recht intensiv auseinandergesetzt hat.
Du schreibst:
"Was du falsch machst, ist, dass du selber Speicher allokierst. Da ist keine Gefahr, sondern es ist schon von vornherein klar, dass du Speicherlecks erzeugst (wenn auch nicht besonders große). Tipp: Allokiere selbst so wenig Speicher wie möglich."Wegen dem Speicher, den ich allokiere - es handelt sich dabei doch zunächst mal um Speicher für mein multidimensionales Objekt, den ich doch auch benötige, weil ich später nach Belieben auf das Obbjekt bzw. seine Unterobjekte zugreifen möchte. Ist es nicht außerdem so, dass ich auch alle erzeugten Unterobjekte ins Jenseits befördere, wenn ich das Objekt o1 mit delete() lösche, weil alle anderen Objekte darin enthalten sind? Natürlich bin ich daran interessiert, überflüssigen Speicherverbrauch zu vermeiden und meinen Programmierstil dahingehend zu optimieren - hast Du vielleicht in dieser Hinsicht konkrete Vorschläge bezogen auf mein Progrämmchen?
Darüber hinaus wäre ich sehr an einer Lösung für das Problem interessiert, dass mir nicht gelingt, das Objekt rekursiv auszulesen.
Auf jeden Fall noch mal vielen Dank für Deine Stellungnahme

-
Ich bin grad sehr beschäftigt, aber ich zeig dir mal was:
MyClass* a = new MyClass(/*Konstruktor-Parameter*/);//Speicher anfordern, Konstruktoraufruf //..damit Arbeiten... delete a;//Destruktoraufruf und Speicherfreigabe (WENN DU ES NICHT MEHR BRAUCHST)Wenn du Ein Objekt, dass als Member Zeiger auf andere Instanzen hat, zerstörst, passiert ganix mit den Pointees dieser Zeiger.
-
c++dummie schrieb:
Ist es nicht außerdem so, dass ich auch alle erzeugten Unterobjekte ins Jenseits befördere, wenn ich das Objekt o1 mit delete() lösche, weil alle anderen Objekte darin enthalten sind?
Nope, die werden nicht automatisch gelöscht. Vector macht zwar seinerseits auch Allokationen, und gibt diese wieder frei, aber da du im Vector nur die Zeiger hältst wird auch nur der Speicher für einen Zeiger (gewöhnlich 4 o 8 Byte) allokiert und friegegeben, aber nicht der Speicher auf den die Zeiger zeigen.
Wenn du unbedingt deinen Speicher selber reservieren willst und im Vector nur die Zeiger halten willst(was ich ausdrücklich nicht empfehle!) dann musst du in deiner Klasse wenigstens einen Desktruktor definieren, der den vector durchläuft und dann deletes auf die Objekte macht.
Wenn du einen Compiler hast, der C++11 unterstützt kannst du dir auch mal std::shared_ptr und std::unique_ptr angucken. Aber mal ehrlich, warum nicht einfachstd::vector<menueitem> subitems; //und dann menueitem item; //mach was mit item subitems.push_back(item);?
-
O.K. Hacker - dass man die Objekte mit delete() wieder wegputzen muss, hatte ich schon begriffen.
JonasOSDever, O.K., das hört sich alles sehr vernünftig an und ich muss mir das Ganze wohl noch mal gründlich überlegen - insbesondere wegen dem Problem, dass ich meine Objekte wegen der Pointer später nur noch mit relativ hohem Aufwand aus dem Speicher herausbekomme. Hast Du schön erklärt!
Danke noch mal allerseits für die aufschlussreichen Tips und Vorschläge bezogen auch das Speichermanagement! Andererseits war es auch so, dass es immer Kompilierungsfehler gehagelt hat, wenn ich irgendwo einen Pointer weggelassen habe - vielleicht wegen der Vererbung - weiß es noch nicht genau.
Jedenfalls fände ich es extrem interessant, mit multidimensionalen Objektstrukturen unter C++ arbeiten zu können(insbesondere auch wegen der geplanten Verarbeitung von Nested Sets aus MySql-Datenbanken ohne dauernde Datenbankzugriffe) - das wäre wahrscheinlich zwar speicherintensiv aber auch extrem performant.
Unter PHP war das wegen dem von Hacker bereits erwähnten Garbage Colletors gar kein Problem und ich habe unter PHP beispielsweise komplette Anwendungen dieser Art für Nested Sets mit GUI programmiert.
Nur kennt man ja die Nachteile, die man sich mit PHP insbesondere bei umfangreicheren Projekten einhandelt - allem voran die im Vergleich zu C bescheidene Performance (selbst bei Verwendung von Funktionscaches) und die relative Unmöglichkeit, sein geistiges Eigentum zu schützen. Aus diesem Grund möchte ich alles, für das sich PHP nicht unbedingt anbietet, in Zukunft eigentlich mit C++ auf die Reihe kriegen.
Vielleicht kann mir bei Gelegenheit ja noch jemand bezogen auf die (vom Speichermanagment doch wohl unabhängige) Frage weiterhelfen, wie ich meinen Objekt-Vektor-Horror rekursiv auslesen kann.
Grüße

-
Hallo noch mal,
Ich habe mal versuchsweise den Vererbungskram raus genommen und die Initialisierung des Vektors in main() veschoben. Ansonsten bin ich so vorgegangen, wie Du schriebst (zumindest, soweit ich das verstanden habe) - slso statische Instantiierung ohne Pointer:
#include <stdio.h> #include <iostream> #include <cstdlib> #include <string> #include <map> #include <vector> using namespace std; class menueItem { public: map <string, string> titles; menueItem(); ~menueItem(){} void show_title(string lang) { cout << this->titles[lang].c_str() << endl; } void set_title(string lang, string title) { this->titles.insert(pair<string, string> (lang, title)); } }; menueItem::menueItem(){} int main() { vector<menueItem> subitems; menueItem o1; o1->set_title("en", "Main Menue"); o1->set_title("de", "Hauptmenü"); }Das war's auch schon, weil mir der Kompiler (g++ (Debian 4.4.5-8) 4.4.5 - kann so gut wie alles) sofort um die Ohren haut, dass er offenbar Probleme damit hat, dass ich keine Pointer verwende, aber geleichwohl die Frechheit besitze, Pfeile zu benutzen.
menue_item3.cpp: In function ‘int main()’: menue_item3.cpp:29: error: base operand of ‘->’ has non-pointer type ‘menueItem’ menue_item3.cpp:30: error: base operand of ‘->’ has non-pointer type ‘menueItem’menueItem o1 = new menueItem;funktioniert ebenfalls nicht - das Einzige, das hilft ist:
menueItem o1* = new menueItem;...und da wären wir wohl wieder beim Zeigerproblem - kriege noch die Krise

-
c+dummie schrieb:
Hallo noch mal,
Ich habe mal versuchsweise den Vererbungskram raus genommen und die Initialisierung des Vektors in main() veschoben. Ansonsten bin ich so vorgegangen, wie Du schriebst (zumindest, soweit ich das verstanden habe) - slso statische Instantiierung ohne Pointer:
#include <stdio.h> #include <iostream> #include <cstdlib> #include <string> #include <map> #include <vector> using namespace std; class menueItem { public: map <string, string> titles; menueItem(); ~menueItem(){} void show_title(string lang) { cout << this->titles[lang].c_str() << endl; } void set_title(string lang, string title) { this->titles.insert(pair<string, string> (lang, title)); } }; menueItem::menueItem(){} int main() { vector<menueItem> subitems; menueItem o1; o1->set_title("en", "Main Menue"); o1->set_title("de", "Hauptmenü"); }Das war's auch schon, weil mir der Kompiler (g++ (Debian 4.4.5-8) 4.4.5 - kann so gut wie alles) sofort um die Ohren haut, dass er offenbar Probleme damit hat, dass ich keine Pointer verwende, aber geleichwohl die Frechheit besitze, Pfeile zu benutzen.
menue_item3.cpp: In function ‘int main()’: menue_item3.cpp:29: error: base operand of ‘->’ has non-pointer type ‘menueItem’ menue_item3.cpp:30: error: base operand of ‘->’ has non-pointer type ‘menueItem’menueItem o1 = new menueItem;funktioniert ebenfalls nicht - das Einzige, das hilft ist:
menueItem o1* = new menueItem;...und da wären wir wohl wieder beim Zeigerproblem - kriege noch die Krise

Haha, OMG!!
Den Pfeiloperator benutzt man bei normalen Zeigern (oder Klassen wie Iteratoren, die ihn überladen). Bei Stackobjekten benutzt man für Memberzugriffe dagegen den Punkt-Operator. Die vorletzte Version von dir ist übrigens komisch - genau genommen Compiliert sie nur dann, wenn menueItem einen Konstruktor mit einem Zeiger entsprechenden Typs als einzigen Parameter hat (dann ist dieser Zeiger zu menueItem konvertierbar).
-
Wie Hacker schon erwähnt hat (den langen Monster-quote erspar ich mir mal) greift man auf Stackobjekte mit . zu.
foo->bar();ist im Prinzip auch nur eine angenehmere Schreibweise für(*foo).bar();Aber dein Versuch auf "Nichtzeiger" mittels -> zuzugreifen zeigt leider auch, dass es dir an den Grundlagen fehlt. Kauf dir bitte ein gutes Anfängerbuch (und auf keinen Fall eins von JW). Deine PHP-Erfahrung ist dir sicherlich beim lernen behilflich, aber es nützt nichts, wenn du nichtmal die Syntax richtig kennst. Ist auch nicht böse gemeint, aber ohne ein ordentliches Buch C++ zu lernen grenzt an die Unmöglichkeit.
-
Jonas OSDever schrieb:
und auf keinen Fall eins von JW
Hey Jonas

es gibt bereits professionelle Indikatoren für schlechte Bücher:Im Titel ist enthalten:
- A bis Z
- 21 Tage (Generell Titel mit Zahlen)
- C/C++
- Schnell
- Gut
- Galileo ComputingUnter anderem: Hat nur Rezensionen auf Amazon, deren Autoren als Bild (schon dass sie ein Bild haben ist ein Indiz) ein professionell geschossenes Foto haben (denn dann sind sie Rezensionsbetrüger, die nur auf äußere Eindrücke Wert legen).
-
das erste Listing von mir war lauffähig und bezogen auf seinen Schwierigkeitsgrad nicht unbedingt trivial. Und - wunderbar - mit dem Punkt Operator funktioniert der Zugriff auf die statische Instanz. Dafür wieder mal dickes Dankeschön.
Ich möchte noch meine Zweifel anmelden, dass man nicht ohne teure Bücher weiterkommt, wenn einem, wie freundlicherweise geschehen, geholfen wird. Z.B. habe ich mir meine PHP-Kenntnisse auch weitgehend ohne Buchsammlung angeeignet, denn im Netz wird man mit Tutorials und dergleichen dazu zugeschüttet - Was C++ angeht, gestaltet sich die Situation schwieriger, weil es grundsätzlich schwerer erlernbar ist aber insbesondere im englischsprachigen Raum findet sich da eine Menge brauchbares Material und nicht zuletzt eben auch in Foren. Heute habe ich z.B. wieder eine Menge gelernt, finde ich und außerdem offenbar zur allgemeinen Belustigung beigetragen.
Spaß beiseite: Sind Foren nicht auch dazu da, Wissen nachzufragen, zu verbreiten und zu vermitteln? Andererseits passt die Nutzlast von dem, was Ihr mich nun schließlich freundlicherweise habt wissen lassen, ohne das gönnerhafte Beiwerk locker in zwei überschaubare Sätze rein - und mit Undank hat das nichts zu tun.
Ich finde gut und bin auch dankbar dafür, Hilfe zu bekommen, helfe anderen auch gerne (zweimal geschehen heute - am ersten Tag) und lasse mich nicht entmutigen - von niemand - wirklich nicht, no go.
In Bezug auf meine eingangs erwähnte Fragestellung könnt ihr mir offenbar auch nicht weiterhelfen - schade eigentlich.
Jedenfalls haben auch Zwerge mal klein angefangen und wenn jeder sofort perfekt wäre, bräuchte es wahrscheinlich keine Entwicklerforen, außer vielleicht, um sich unter Experten gegenseitig auf die Schulter zu klopfen - in diesem Sinne nochmals vielen Dank.
-
Du kannst wirklich vieles im Netz erlernen. Mir käme es einfach nie in den Sinn, für eine C++-API/Lib ein Buch zu kaufen (das machen nur Leute die zu viel Geld haben). Bei C++ ist es gerade andersherum.
-
Hacker schrieb:
es gibt bereits professionelle Indikatoren für schlechte Bücher:
Im Titel ist enthalten:
- A bis Z
- 21 Tage (Generell Titel mit Zahlen)
- C/C++
- Schnell
- Gut
- Galileo ComputingDer größte Indikator bleibt weiterhin wenn JW (ich will den Namen nichmal ausschreiben) als Autor draufsteht.
"Grundkurs C++" - keines der Oben angegeben Merkmale zieht (Galileo steht zumindest nicht im Title
).@c+dummie: ich will dich weder entmutigen noch dir empfehlen dir die teuersten Bücher kaufen. Fakt ist aber das die Tutorials im Netz zu C++ meistens mäßig bis schlecht sind - keine Ahnung wies bei PHP ist.
Du kannst ja vorerst bei den Tutorials bleiben, aber auf längere Sicht würde ich trotzdem einen Buchkauf in Erwägung ziehen (im Zweifelsfall zum Geburtstag auf Verwandte abwimmlen
)
-
Das einzig gute war das von volkard, aber das ist mittlerweile auch entweder runter oder auch veraltet...
-
@Hacker - Da Letzte, was ich vorhabe, ist in C-Style zu verfallen oder so weiter. Ich will von vorneherein mit allen Schikanen obejektorieniert programmieren - alles andere kann man heutzutage von vorneherein vergessen - keine Chance.
Ich habe das Teil nochmals umgeschrieben und auf Anraten sämtliche Zeiger rausgeschmissen - auf die Gefahr hin, mich noch mal lächerlich zu machen:
#include <stdio.h> #include <iostream> #include <cstdlib> #include <string> #include <map> #include <vector> using namespace std; class menueItemBase { public: map <string, string> titles; menueItemBase(); ~menueItemBase(){} void show_title(string lang) { cout << this->titles[lang].c_str() << endl; } void set_title(string lang, string title) { this->titles.insert(pair<string, string> (lang, title)); } }; menueItemBase::menueItemBase(){} class menueItem: public menueItemBase { public: vector<menueItemBase>subitems; menueItem(); ~menueItem(){} void show_subitems(string lang) { for (unsigned i = 0; i < this->subitems.size(); i++) { this->subitems[i].show_title(lang); //this->subitems[i].show_subitems(lang); } } }; menueItem::menueItem() {} int main() { menueItem o1; o1.set_title("en", "Main Menue"); o1.set_title("de", "Hauptmenü"); menueItem o2; o2.set_title("en", "Edit"); o2.set_title("de", "Bearbeiten"); menueItem o3; o3.set_title("en", "Preferences"); o3.set_title("de", "Einstellungen"); o2.subitems.push_back(o3); o1.subitems.push_back(o2); o1.show_subitems("de") }Wenn ich die auskommentierte Zeile einkommentiere...
this->subitems[i].show_subitems(lang);...fliegt es mir nach wie vor um die Ohren:
menue_item3.cpp: In member function ‘void menueItem::show_subitems(std::string)’: menue_item3.cpp:40: error: ‘class menueItemBase’ has no member named ‘show_subitems’Hat wirklich niemand 'ne Idee bezogen auf dieses Rekursionsproblem?
Wie kriege ich das hin (außer mit 'gutem Buch kaufen')?
-
Der Compiler hat immer Recht. Die Fehlermeldung ist eindeutig und sehr leicht zu interpretieren. Wo ist das Problem?
-
Das Problem besteht darin, dass entweder ich persönlich es offenbar nicht lösen kann, das Problem auf die von mir angedachte Art und Weise nicht zu lösen ist oder aber (und das wäre wirklich knackig): wenn es auf eine Art und Weise zu lösen ist, die ich nicht kenne und mir einfach jemand zeigt, wie.
Ich halte das noch nicht für gänzlich hoffnungslos. Zwei Foren habe ich heute parallel beackert, morgen vormittag kann ich nicht und morgen nachmittag werde ich weiter rummachen - dann auch international, obwohl es einen Tick schwieriger ist, mein Anliegen auch noch z.B. in korrektes Englisch zu übersetzen - geht aber - Aufwand ca 25% höher.
Gute Nacht - mache jetzt 'nen Abgang

-
Jetzt bin ich doch noch mal kurz aus dem Halbschlaf erwacht
Mir war fast, als würde mal eben jemand dazu anschicken, mir nichtswürdigem Etwas einen grandiosen Codeschnipsel zuzuschieben, der mich endgültig von allen Portierungsproblemen erlöst. Super - morgen ist auch noch ein Tag!
-
Der compiler sagt dir ‘class menueItemBase’ has no member named ‘show_subitems’.
Jetzt such mal in deinem menuItemBaseclass menueItemBase { public: map <string, string> titles; menueItemBase(); ~menueItemBase(){} void show_title(string lang) { cout << this->titles[lang].c_str() << endl; } void set_title(string lang, string title) { this->titles.insert(pair<string, string> (lang, title)); } };nach show_subitems und sag mir, wo das ist.
Kleiner Hinweis: Ich finde es nicht.Jetzt kannst du dir noch überlegen, warum der compiler in menuItemBase diese Funktion sucht. Schau dir dazu mal den vector subitems an.
-
class menueItemBase { public: map <string, string> titles; menueItemBase(); ~menueItemBase(){} void show_title(string lang) { cout << this->titles[lang].c_str() << endl; } void set_title(string lang, string title) { this->titles.insert(pair<string, string> (lang, title)); } virtual void show_subitems(string lang) {} //als überschreibbar definieren //alternativ: virtual void show_subitems(string lang) = 0; };Und dann in menueItem:
class menueItem : public menueItemBase { virtual void show_subitems(string lang) //überschreiben { //mach irgendwas } };virtual wird in C++ für die Überschreibung benutzt, aus PHP müsstest du ja wissen was das ist.
Die Variante in menueItemBase, die ich nicht auskommentiert hab ist wie du siehst einfach leer und wird dann in menueItem überschrieben.
Die auskommentierte Variante ist eine pure-virtual Funktion. Dann kannst du aber von der Klasse keine Instanzen mehr erzeugen.Allerdings können im Vector wieder nur Pointer auf eine Instanz von menueItemBase abgelegt werden, denn sonst wird nicht die überschriebene Variante aufgerufen, wodurch wir wieder bei dem Problem vom Anfang wären, dass du den Speicher selbst freigeben musst.
Schau dir bitte mal shared_ptr und unique_ptr an. Denn die geben ihren Speicher automatisch frei, wenn der vector der sie beinhaltet zerstört wird.BTW: Warum brauchst du überhaupt eine Basisklasse mit virtuellen Methoden, wenn du eh nur eine Klasse ableitest?