Multidimensionale Menüstruktur mit OOP und stl::vector



  • Erst mal vielen Dank für die neuen, interessanten Beiträge hier!!!

    @Jonas OSDever

    Alles im Lot - Dein hochinteressantes Listing läuft absolut rund, womit ich vermutlich alles beisammen habe, was ich für die weitere Umsetzung meiner Menüstruktur benötige. Es kann natürlich auch im Weiteren immer mal wieder zu Problemen kommen, weil ich halt von beinahe Allem wenig Ahnung habe. Andererseits habe ich erheblich dazugelernt.

    Ich hoffe, Dich richtig so verstanden zu haben, dass sich jedwedes delete() in Deinem Programm erübrigt.

    @debianer

    Mein Inkludierungsproblemchen (#include <memory>) hatte ich schon selbst in den Griff bekommen, bevor ich hier nochmals reingeschaut hatte, trotzdem danke - schön außerdem, hier auch Experten für mein Lieblings-OS anzutreffen.

    Soweit ich weiß, ist die neuste GGC-Version 4.6 - meine ist(Debian 4.4.5-8) 4.4.5

    Auch mit einem knappen g++ -o jonas1 -std=c++0x jonas1.cpp habe ich das Listing schließlich ohne Probleme kompilieren können. Daher ist mir nicht unbedingt klar, wozu die anderen Schalter dienen (-Wall -Wextra -pedantic). Werde mich gleich mal schlau machen.

    @MJM
    Dein PHP-Listing ist so, wie es da steht lauffähig, obwohl es mir zuerst den Angstschweiß auf die Stirn getrieben hat - so was kommt mir zum ersten Mal unter 🙂

    Es mag daran liegen soll, das ich unter PHP noch nie das Bedürfnis hatte eine Klasse als Funktionsargument zu übergeben. Dein Programm läuft auch, wenn man das nicht tut, also function showHelloWorld($bla) geht auch.

    Zwar ist das off-topic aber zum Thema PHP möchte ich noch ganz nebenbei erwähnen, wie lächerlich einfach die ganze Aufgabe damit zu lösen ist (Kinderkram) - keine Klassen für benutzerrdefinierte Vektoren und virtuelle Funktionen ect... - z.B.:

    <?php
    class menueItem
    {
        private $value, $items;
    
        public function __construct($value)
        {
            $this->value = $value;
        }
    
        public function append_object($obj)
        {
            $this->items[] = $obj;
        }
    
        public function show_object_values()
        {
            echo "\n<pre>".$this->value."</pre>";
            if(isset($this->items))
            {
                foreach ($this->items as $item)
                {
                    $item->show_object_values();
                }
            }
        }
        public function __destruct(){}
    }
    
    $obj1 = new menueItem("Menüstruktur");
    $obj2 = new menueItem('Bearbeiten');
    $obj3 = new menueItem('Einstellungen');
    $obj4 = new menueItem('Plugins');
    
    $obj3->append_object($obj4);
    $obj2->append_object($obj3);
    $obj1->append_object($obj2);
    
    $obj1->show_object_values($obj1);
    ?>
    

    Ausgabe:

    Menüstruktur
    Bearbeiten
    Einstellungen
    Plugins



  • Der Code 1:1 nach C++ übernommen:

    #include<iostream>
    #include<string>
    #include<vector>
    
    class menueItem {
    private:
    	std::string value;
    	std::vector<menueItem> items;
    
    public:
    	menueItem(std::string const& value)
    	: value(value)
    	{}
    
    	void append_object(menueItem const& obj) {
    		items.push_back(obj);
    	}
    
    	friend std::ostream& operator<<(std::ostream& out, menueItem const& item) {
    		out<<item.value<<"\n";
    		std::vector<menueItem>::const_iterator i=item.items.begin(), end=item.items.end();
    		while(i!=end) {
    			out<<*i;
    			++i;
    		}
    		return out;
    	}
    };
    
    int main() {
    	menueItem obj1("Menüstruktur");
    	menueItem obj2("Bearbeiten");
    	menueItem obj3("Einstellungen");
    	menueItem obj4("Plugins");
    
    	obj3.append_object(obj4);
    	obj2.append_object(obj3);
    	obj1.append_object(obj2);
    
    	std::cout<<obj1<<"\n";
    }
    

    sehe da keinen einzigen relevanten Unterschied.
    Ich war so frei und habe den operator<< statt show_values() genommen - aber im Prinzip ists das gleiche.

    PS:
    Der Code ist so halt Schrott - ist er in PHP aber auch. Weil man nix damit machen kann.



  • "Der Code ist so halt Schrott - ist er in PHP aber auch. Weil man nix damit machen kann."

    Um auf dieses pauschale und vernichtende Urteil eines offenbar äußerst verdienten Teilnehmers Bezug zu nehmen:

    Insbesondere Deine Schlussbehauptung kann ich so nicht stehen lassen, weil die Tatsache, das etwas, mit dem man nichts machen kann, weil es lediglich ein Beispiel für einen Bestandteil von etwas anderem ist, noch lange nicht bedeutet, dass das Gesamtgebilde Schrott ist. Außerdem war die Ausgangslage lediglich die, dass ich mich bemüht habe, ein Problem trotz ziemlich grundlegender Anfängerprobleme so darzustellen, dass es zu verstehen und einfach zu bearbeiten ist.

    echo; ist auch Schrott, weil man nix damit machen kann und weil es nichts macht.
    echo "Hello World"; ist nach meinem Verständnis dagegen kein Schrott, weil dabei unten was mehr oder weniger Sinnvolles herauskommt.

    Ich kann zumindest mit meiner PHP-Variante genau das, was ich tue: Multidimensionale Objekte erzeugen und sie wieder auslesen. Im Übrigen kann ich das nun auch mit C++ - zumindest wenn ich hier durch bin.

    Zum praktischen Nutzen: nach entsprechender Anpassung kann ich zum Beispiel unter PHP einen häufig verwendeten Nested Set aus einer Datenbank auslesen oder eine XML-Datei aus dem Dateisystem, selbiges auf diese Weise darstellen, im Arbeitsspeicher cachen (memcached) und erreiche hierdurch selbst unter PHP vergleichsweise niedrige Latenzen (ohne dauernde Zugriffe auf Datenbanken oder das Dateisystem).

    In vielen Fällen wäre es auch praktischer, derartige Aufgaben mit Hilfe multidimensionaler und teilweise assoziativer Arrays umzusetzen, muss es aber nicht (z.B. dann nicht, wenn die Datenbasis ihrerseits aus serialisierten Objekten besteht).

    Wie dem auch sei - danke insbesondere für Dein Listing. Ich werde es mir ebenfalls eingehend zu Gemüt führen - schönen Abend noch.



  • c+dummie schrieb:

    Insbesondere Deine Schlussbehauptung kann ich so nicht stehen lassen, weil die Tatsache, das etwas, mit dem man nichts machen kann, weil es lediglich ein Beispiel für einen Bestandteil von etwas anderem ist, noch lange nicht bedeutet, dass das Gesamtgebilde Schrott ist. Außerdem war die Ausgangslage lediglich die, dass ich mich bemüht habe, ein Problem trotz ziemlich grundlegender Anfängerprobleme so darzustellen, dass es zu verstehen und einfach zu bearbeiten ist.

    Die richtige Art eine Menustruktur zu machen wurde hier schon genannt. Es laeuft ueber Polymorphie. Schliesslich willst du ja nicht nur einen Text ausgeben sondern ein Menupunkt hat bestimmtes verhalten. uU hat er einen Helptext und aufjedenfall hat er eine Aktion.

    Wenn wir zB folgenden Code haetten:

    #include<iostream>
    #include<string>
    #include<vector>
    
    class MenuItem {
    private:
        std::string name;
    	std::vector<MenuItem*> items; //besser waere boost::ptr_vector
    
    public:
        MenuItem(std::string const& name)
        : name(name)
        {}
    
    	virtual ~MenuItem() {
    	    std::vector<MenuItem*>::const_iterator i=items.begin(), end=items.end();
            while(i!=end) {
                delete *i;
                ++i;
            }
    	}
    
        std::string const& getName() const {
    		return name;
    	}
    
    	void append(MenuItem* obj) {
            items.push_back(obj);
        }
    
    	virtual void action() = 0;
    
    	virtual void displayChildren(std::ostream& out) const {
            std::vector<MenuItem*>::const_iterator i=items.begin(), end=items.end();
            while(i!=end) {
    			(*i)->display(out);
                ++i;
            }
    	}
    
    	virtual void display(std::ostream& out) const {
            out<<name<<"\n";
    	}
    
    };
    std::ostream& operator<<(std::ostream& out, MenuItem const& item) {
    	item.display(out);
    	item.displayChildren(out);
    	return out;
    }
    
    class SeparatorMenuItem : public MenuItem {
    public:
    	SeparatorMenuItem(std::string const& name)
    	: MenuItem(name)
    	{}
    
    	void action() {
    		//donothing
    	}
    
    	void display(std::ostream& out) const {
    		out<<" --- "<<getName()<<" --- \n";
    	}
    };
    
    class CallFuncMenuItem : public MenuItem {
    public:
    	typedef void (*CallFuncType)();
    private:
    	CallFuncType callFunc;
    public:
    	CallFuncMenuItem(std::string const& name, CallFuncType callFunc)
    	: MenuItem(name), callFunc(callFunc)
    	{}
    
    	void action() {
    		callFunc();
    	}
    };
    
    class PrintTextMenuItem : public MenuItem {
    private:
    	std::string text;
    public:
    	PrintTextMenuItem(std::string const& name, std::string const& text)
    	: MenuItem(name), text(text)
    	{}
    
    	void action() {
    		std::cout<<text<<"\n";
    	}
    };
    
    void bearbeiten() {
    	std::cout<<"do some stuff\n";
    }
    
    int main() {
    
    	SeparatorMenuItem base("Menue");
    
    	base.append(new CallFuncMenuItem("Bearbeiten", bearbeiten));
    	base.append(new PrintTextMenuItem("Plugins", "not yet implemented"));
    
        std::cout<<base<<"\n";
    }
    

    Dann haben wir ploetzlich polymorphes verhalten im Menu. Wir koennen komplexere Strukturen darstellen weil wir automatisch untermenues haben. Wir haben unterschiedliche Aktionen die pro Menu Item aufgerufen werden koennen, etc.

    So in etwa sollte der Code aussehen.

    PS:
    Wenn du natuerlich alles statisch haben willst: wozu so eine Struktur? Dann tut es ein PHP array bzw. std::vector auch. Aber die wirkliche Macht von so einer Struktur bekommst du nur durch Polymorphie.

    Schau dir zB mal an wie man so ein Menu in JavaScript machen wuerde. Da hat man pro Item ja eine Aktion. Dort wuerde man zu jedem Item unterschiedliche Callbacks hinterlegen. Und bedenke: verschachtelte Items sollten auch moeglich sein...



  • Bisher bin ich nicht davon ausgegangen, dass es Sinn macht, so einem unwürdigen Menüpunkt auch noch ein eigenes Verhalten zuzubilligen 🙂 Mit dieser Perspektive habe ich mich bisher noch nicht mal theoretisch auseinandergesetzt. Für mich war so ein Menü zunächst mal eine hierarchich strukturierte Angelegenheit, die es zu behandeln gilt, wenngleich es durchaus Sinn macht, dass selbst so ein an sich beschränkter Menüpunkt selbst sich Zeit seines Lebens und von vorne herein bereits darüber im Klaren sein sollte, was ihm das Schicksal in die Wiege gelegt hat 🙂

    Ich muss Dich aber trotzdem für heute insofern unterbrechen, als ich wirklich in Bezug auf solche Feinheiten (oder das grundlegende Verständnis, wenn Du so willst) auf der Skala relativ weit unten anzusiedeln bin - Du da oben im 7. Himmel der objektorientierten C++ Programmierung - ich: ein Wurm, der geradezu herausfordert, zertreten zu werden.

    Wie auch immer - ich bin nicht wirklich ein Wurm, aber um aufzuarbeiten, womit Du mich eben konfrontiert hast, benötige ich mit Sicherheit Tage, wenn nicht Wochen meiner kostbaren Lebenszeit - ich muss mich jetzt erst mal entspannen.

    Würmer machen sich oft keinen Begriff davon, was sie so alles können sollten, bevor sie die falschen Fragen stellen 🤡

    Ich betrachte das hier aber nicht als mein Terretorium - nur kann ich nicht mit Dir nicht über Deine Listings diskutieren, außer sie unreflektiert zu bestaunen, weil, ich sie (noch) nicht aufarbeiten kann. Die Lernkurve ist zu steil aber das muss ja nicht so bleiben.

    Gute Nacht für heute, danke noch mal!



  • @Shade
    Ich hab da ne Frage. In Z. 35 & 18 benutzt du statt einer einfachen for-Schleife dieses while-Ding. Mir ist dein Code-Stil egal, aber hat das irgendeinen Grund den ich nicht kenne, bzw. überseh' ich da was? Ist schon irgendwie ungewöhnlich, genau für sowas gibt's doch die for-Schleife und ich finde das deutlich übersichtlicher als das was Du da benutzt, weil man sofort sieht, wo Zählvariable, Bedingung und Handlung sind..

    EDIT: Klar meine ich 35..



  • Incocnito schrieb:

    @Shade
    Ich hab da ne Frage. In Z. 42 & 18 benutzt du statt einer einfachen for-Schleife dieses while-Ding. Mir ist dein Code-Stil egal, aber hat das irgendeinen Grund den ich nicht kenne, bzw. überseh' ich da was? Ist schon irgendwie ungewöhnlich, genau für sowas gibt's doch die for-Schleife und ich finde das deutlich übersichtlicher als das was Du da benutzt, weil man sofort sieht, wo Zählvariable, Bedingung und Handlung sind..

    Nein, Shades Coding style ist schön. Es ist viel schöner das separat zu schreiben, als alles in eine einzige Zeile zu packen, nur für das bisschen lokalisierung.

    Angenommen du meinst Z. 35 😃



  • Worin ich ein potentielles Problem sehe, ist die rekursive Löschung der erzeugten Objekte in Hinblick auf mögliche Speicherlecks, weil hierbei (sofern ich die Methodik richtig verstanden habe) nicht von oben nach unten verfahren werden kann, denn wenn ein Objekt gelöscht wurde, komme ich doch an den Speicherplatz, den die Unterobjekte belegen, gar nicht mehr heran. Von daher vielleicht doch lieber std::unique_ptr oder boost?



  • c+dummie schrieb:

    denn wenn ein Objekt gelöscht wurde, komme ich doch an den Speicherplatz, den die Unterobjekte belegen, gar nicht mehr heran.

    Das ist doch der Witz bei rekursivem Löschen: du rufst rekursiv zuerst die Löschmethoden/delete für die UNterobjekte auf, danach fürs Oberobjekt.

    Trotzdem ist unique_ptr auf jeden Fall eine gute Idee.



  • Ich verwende hier while weil ein for ziemlich messy aussieht. Und es schwer ist auf den ersten Blick zu erkennen was Sache ist. Meistens ziehe ich i und begin auch noch jeweils in eigene Zeilen.

    Ein

    std::vector<MenuItem*>::const_iterator i=items.begin();
    std::vector<MenuItem*>::const_iterator end=items.end();
    while(i!=end) {
      ...
    }
    

    finde ich viel viel viel besser als ein

    for(std::vector<MenuItem*>::const_iterator i=items.begin(), end=items.end();i!=end; ++i) {
      ...
    }
    

    uU koennte man natuerlich besser umbrechen um die gleiche lesbarkeit zu erreichen. Aber prinzipiell will ich beim lesen des Codes "i!=end" sehen. Das ist der springende Punkt. Mit auto gehts natuerlich dann schoener...

    zum loeschen: ich habe ja geschrieben dass ich hier boost::ptr_vector bevorzugen wuerde - aber ich wollte boost nicht verlangen fuer den Code. vector<smart_ptr> gefaellt mir vom Prinzip her garnicht - das vermeide ich. Fuer sowas gibts naemlich boost::ptr_container.

    Warum das ganze:
    Weil man immer verhalten haben will. Welchen Vorteil haette das ganze gegenueber einem vector<string> wenn MenuItem keine Funktionalitaet bietet? Normalerweise will man ja Menupunkte auch auswaehlen koennen und die sollen dann etwas tun. Nur anzeigen ohne dass ich eine Aktion darauf setzen kann ist etwas schwach 😉

    Und diese Aktion eben. zB neues Fenster oeffnen oder gewisse Daten anzuzeigen muss mit dem Menupunkt irgendwie verknuepft werden.

    PS:
    ich habe den Code in 2 Minuten oder so geschrieben - da steckt nicht uebermaessig viel Nachdenken drinnen... Es soll nur grob skizzieren wie es aussehen koennte/sollte.


Anmelden zum Antworten