Typ einer verketteten Liste zur Laufzeit entscheiden?



  • Hallo,
    ich würde gerne vom Benutzer eingeben lassen, von welchem Typ meine Liste erstellt werden soll. Es wird zb angezeigt "1: int 2: char" und der Benutzer kann dann auswählen was er benutzen möchte. Geht das? Und wenn ja, wie? Ich habe da keine Ahnung wie ich daran gehen soll.
    Hier noch mein Code für die Liste, ohne der Implementation der Methoden:

    #include <iostream>
    #include <limits> //fuer falscheingaben
    #include <conio.h> //fuer getch()
    
    //der knoten
    template <typename T>
    class Knoten {
    	public:
    	 T daten; //die daten
    	 Knoten *naechstes; //zeiger auf einen nachfolger
    };
    
    template <typename T>
    class Liste {
    	private:
    	 Knoten<T> *Anker; //ausgangspunkt der liste
    	 unsigned int anz; //anzahl der listenelemente
    	public:
    	 Liste() : Anker(0), anz(0) { } //liste initialisieren
    	 void hinzufuegen(T); //ein objekt hinzufuegen
    	 T loeschen(); //das oberste objekt vom stapel holen
    	 void anzeigen() const; //die liste anzeigen lassen
    	 bool finde(T) const; //ein objekt suchen
    	 bool loesche(unsigned int pos); //ein element an position pos von der liste entfernen
    	 void leere(); //liste leeren
    	 int anzahl() const { return anz; } //anzahl der elemente ermitteln (außerhalb)
    	 bool leer() const { return !Anker; } //pruefen ob liste leer
    };
    
    //definition der methoden
    
    int main() {
    	using namespace std;
    	// Hier würde ich gerne eingeben lassen von welchem Typ die Liste sein soll.
    	Liste</*und jetzt?*/> meineListe; //Liste erstellen
    }
    

    Danke schonmal im voraus 🙂



  • Das Übliche: Abstrakte Basisklasse und zwei Implementierungen, die davon erben. Die Fallunterscheidung machst du bei zwei Fällen mit If.

    Und du kennst std::list ?



  • #include <vector>
    #include <iostream>
    #include <string>
    
    class Value
    {
    	public:
    		enum Type
    		{
    			INT,
    			STRING
    		};
    
    	protected:
    		Type type_;
    
    	public:
    		virtual Type getType() const { return type_; }
    };
    
    class ValueInt : public Value
    {
    	int value_;
    
    	public:
    		ValueInt(int i) : value_(i)
    		{
    			type_ = INT;
    		}
    
    		int getValue() const { return value_; }
    };
    
    class ValueString : public Value
    {
    	std::string value_;
    
    	public:
    		ValueString(const std::string& str) : value_(str)
    		{
    			type_ = STRING;
    		}
    
    		std::string getValue() { return value_; }
    };
    
    typedef std::vector<Value *> ValueVec;
    
    int main()
    {
    	ValueVec vec;
    
    	vec.push_back(new ValueString("String"));
    	vec.push_back(new ValueInt(12345));
    
    	for (ValueVec::iterator it = vec.begin(); it != vec.end(); ++it)
    	{
    		switch ((*it)->getType())
    		{
    			case Value::INT:
    				std::cout << "Int: " << dynamic_cast<ValueInt *>(*it)->getValue() << std::endl;
    				break;
    
    			case Value::STRING:
    				std::cout << "String: \"" << dynamic_cast<ValueString *>(*it)->getValue() << "\"" << std::endl;
    				break;
    		}
    	}
    }
    


  • Explizite Fallunterscheidungen sind unschön und fehleranfällig. Benutze virtuelle Funktionen.

    Abgesehen davon kann man zwei abgeleitete Klassen für die ganze Liste und nicht nur ihre Elemente erstellen. Dann kann man immerhin garantieren, dass eine Liste nur aus Elementen vom gleichen Typ besteht, und kennt diesen Typ innerhalb der Liste.

    Und Speicher freigeben wäre auch eine Idee...



  • kann man eigentlich einen zeiger auf eine template klasse erstellen ohne den typ anzugeben?
    um eben diesen dann beim initialisieren anzugeben?

    also sowas:

    std::vector * vec = nullptr;
    if ( true )
       vec = new std::vector<int>(235435);
    else
       vec = new std::vector<float>(3578l);
    

    denke mal nicht dass das so geht, aber gibts da eine ähnliche möglichkeit?

    weil gra den standard klassen kann man ja nicht einfach so mal schnell ne oberklasse geben



  • Die dynamischen Wochen scheinen herangebrochen zu sein!



  • Ich hab das ganze jetzt mit Speicherbeschaffung ausprobiert da ich mich mit Vererbung noch nicht wirklich auskenne, und heraus kam dann das hier:

    template <typename T>
    class Liste {
    	//...
    	public:
    	 //wie gehabt
    	 static Liste<T>* meineListe;
    };
    
    //...
    int main() {
    	using namespace std;
    	cout << "Waehle einen Datentyp aus: \n"
    	     << "1:\tint\n2:\tchar\n3:\tstd::string\n\nDeine Wahl: ";
    	char welcherTyp = getch();
    	switch(welcherTyp) {
    		case '1': Liste<int>::meineListe = new Liste<int>; break;
    		case '2': Liste<char>::meineListe = new Liste<char>; break;
    		case '3': Liste<string>::meineListe = new Liste<string>; break;
    	}
            //...
            switch(welcherTyp) {
    			 	case '1': Liste<int>::meineListe->macheWas(); break;
    			 	case '2': Liste<char>::meineListe->macheWas(); break;
    			 	case '3': Liste<string>::meineListe->macheWas(); break;
    			 }
    

    Jetzt bekomme ich allerdings überall "undefined reference to". Heißt das, ich soll zu jedem der 3 Datentypen eine Spezialisierung machen? Was mache ich falsch?
    WIe gesagt, ich kenn mich mit Vererbung nicht wirklich aus. WIe regel ich das mit Speicherbeschaffung?



  • Skym0sh0 schrieb:

    kann man eigentlich einen zeiger auf eine template klasse erstellen ohne den typ anzugeben?
    um eben diesen dann beim initialisieren anzugeben?

    also sowas:

    std::vector * vec = nullptr;
    if ( true )
       vec = new std::vector<int>(235435);
    else
       vec = new std::vector<float>(3578l);
    

    denke mal nicht dass das so geht, aber gibts da eine ähnliche möglichkeit?

    weil gra den standard klassen kann man ja nicht einfach so mal schnell ne oberklasse geben

    Nein, da Templates eben statische Polymorphie sind und dieser Anwendungsfall hier dynamische (d.h. Laufzeit-)polymorphie wäre.

    @Incognito:
    Ich schlage Dir vor, erstmal alle Konzepte von C++ zu lernen, da schleichen sich sonst sehr unschöne Fehler ein.

    template <typename T>
    class Liste {
        //...
        public:
         //wie gehabt
         static Liste<T>* meineListe;
    };
    
    //...
    int main() {
        using namespace std;
        cout << "Waehle einen Datentyp aus: \n"
             << "1:\tint\n2:\tchar\n3:\tstd::string\n\nDeine Wahl: ";
        char welcherTyp = getch();
        switch(welcherTyp) {
            case '1': Liste<int>::meineListe = new Liste<int>; break;
            case '2': Liste<char>::meineListe = new Liste<char>; break;
            case '3': Liste<string>::meineListe = new Liste<string>; break;
        }
            //...
            switch(welcherTyp) {
                     case '1': Liste<int>::meineListe->macheWas(); break;
                     case '2': Liste<char>::meineListe->macheWas(); break;
                     case '3': Liste<string>::meineListe->macheWas(); break;
                 }
    

    Puh, das finde ich sehr hässlich. Du musst dir "welcherTyp" jetzt erstmal irgendwo speichern, um am Ende das delete machen zu können. Außerdem spart deine Methode nichts, weil Du im Speicher jetzt Liste<int>::meineListe, Liste<char>::meineListe ... stehen hast. Sobald das im Code vorkommt, werden die statischen Objekte der Klassen (und bedenke: jede Templateklasse (also jedes mit einem Argument verwendete Klassentemplate) ist eine eigene Klasse, du hast hier also effektiv drei Klassen) erstellt.

    Incognito, wieso soll der Benutzer sich den Typ aussuchen können? In welchem größeren Rahmen soll das eine Rolle spielen?



  • Skym0sh0 schrieb:

    kann man eigentlich einen zeiger auf eine template klasse erstellen ohne den typ anzugeben?

    Man kann es schon deshalb nicht, weil es sich nicht um eine Template-Klasse handelt, sondern um eine Klassen-Template bzw. Klassenvorlage. Erst durch die Konkretisierung anhand der Template-Parameter erhält man eine Klasse - Liste<int> ist eine ganz andere Klasse als Liste<char>.



  • Incocnito, hör auf Eisflamme und wirf den Code fort, sonst wird das äusserst mühsam. Wenn du eine brauchbare Lösung für dein Problem suchst, befasse dich eben mit Vererbung und dynamischer Polymorphie, statt nur zu sagen, dass du dich mit ihr nicht auskennst. Darum herum kommst du eh nicht, wenn du C++ programmierst.

    Und hättest du meinen Post gelesen, sollte dir bei jedem switch auf Typ-Identifizierer eine Alarmglocke schrillen.



  • Ich sagte nur, dass ich mich noch nicht mit ihr auskenne, nicht, dass ich mich nicht mit ihr beschäftige (was ich seit ein paar Tagen bereits tue ;))
    Und gelesen hab ich deinen Post.

    Nexus schrieb:

    Und hättest du meinen Post gelesen, sollte dir bei jedem switch auf Typ-Identifizierer eine Alarmglocke schrillen.

    Bin ich blind oder wo hast du soetwas erwähnt 😕
    Naja jetzt weiß ich, dass, wenn ich jedesmal Liste<X> im Code stehen hab, eine eigene Klasse dafür erstellt wird. (Laut Eisflamme)

    Außerdem war das nur eine praktische Übung, in der "Praxis" verwende ich logischerwiese std::list .

    Eisflamme schrieb:

    Incognito, wieso soll der Benutzer sich den Typ aussuchen können? In welchem größeren Rahmen soll das eine Rolle spielen?

    Keine Ahnung, ich wollte nur mal wissen wie das gehen könnte. Reine Neugierde. Praktisch zu verwenden ist meine Liste ja sowieso nicht, für sowas benutze ich std::list .
    Danke für die Antworten 🙂



  • Okay. Wenn man ein wenig mit der Sprache rumspielt am Anfang, macht man Zeug, wo man später weiß dass man es nicht benötigt. Man würde für einen eventuellen Anwendungsfall nie (oder äußerst selten) zu dem programmiertechnischen Problem kommen, so etwas zu bauen.



  • Mit "Explizite Fallunterscheidungen sind unschön und fehleranfällig" meinte ich, du solltest switch zur Unterscheidung zwischen mehreren Typen vermeiden. Das ist nämlich meistens ein starker Hinweis auf schlechtes Design und wird durch den Einsatz virtueller Funktionen überflüssig.


Log in to reply