Polymorphie in Templates



  • skNiNe schrieb:

    Aber selbst wenn nicht, würde mich interessieren wie man sowas macht (falls sowas geht).

    Also generell kann man eine Wrapper-Klasse schreiben...
    Hier nur mal etwas Beispiel-Code, ich weiß nicht, ob es einfacher geht...

    Aber für den Zuweisungsoperator oder Copy-Konstruktor, braucht man wohl intern einen Zeiger auf eine Klasse mit virtuellen Funktionen

    An der Komplexität siehst du, dass es wohl eher eine theoretische Möglichkeit einer Lösung ist.

    #include <iostream>
    #include <vector>
    
    template<typename T>
    class InternalVectorWrapper{
    public:
    	virtual ~InternalVectorWrapper(){}
    	virtual InternalVectorWrapper<T>* copy() const = 0;
    	virtual T& operator[](unsigned int i) = 0;
    	virtual unsigned int size() = 0;
    };
    
    template<typename Type, typename TBase>
    class SpecificVectorWrapper : public InternalVectorWrapper<TBase>{
    public:
    	SpecificVectorWrapper(std::vector<Type>& list) : list(list){
    	}
    
    	virtual InternalVectorWrapper<TBase>* copy() const {
    		return new SpecificVectorWrapper<Type, TBase>(*this);
    	}
    	virtual TBase& operator[](unsigned int i){
    		return list[i];
    	};
    
    	virtual unsigned int size(){
    		return list.size();
    	}
    
    private:
    	std::vector<Type>& list;
    };
    
    template<typename TBase>
    class VectorWrapper{
    public:
    	template<typename T>
    	VectorWrapper(std::vector<T>& t) : ptr(new SpecificVectorWrapper<T, TBase>(t)){}
    	~VectorWrapper(){ delete ptr; }
    
    	VectorWrapper(const VectorWrapper<TBase>& w){
    		this->ptr = w.ptr->copy(); 
    	}
    
    	// Zuweisungsoperator implementieren...
    
    	TBase& operator[](unsigned int i){
    		return ptr->operator[](i);
    	}
    
    	unsigned int size(){
    		return ptr->size();
    	}
    
    protected:
    	InternalVectorWrapper<TBase>* ptr;
    };
    
    class Fruit{
    public:
    	virtual ~Fruit(){}
    	virtual void print_name() = 0;
    };
    
    class Banana : public Fruit{
    public:	
    	virtual void print_name(){
    		std::cout << "banana" << std::endl;
    	}
    };
    
    class Apple : public Fruit{
    public:	
    	virtual void print_name(){
    		std::cout << "apple" << std::endl;
    	}
    };
    
    class FruitBasket{
    public:
    	virtual VectorWrapper<Fruit> get_list(); 
    };
    
    class BananaBasket{
    public:
    	BananaBasket(){
    		basket.push_back(Banana());
    		basket.push_back(Banana());
    	}
    
    	virtual VectorWrapper<Fruit> get_list(){
    		return basket;
    	}
    
    private:
    	std::vector<Banana> basket;
    };
    
    class AppleBasket{
    public:
    	AppleBasket(){
    		basket.push_back(Apple());
    		basket.push_back(Apple());
    		basket.push_back(Apple());
    	}
    
    	virtual VectorWrapper<Fruit> get_list(){
    		return basket;
    	}
    
    private:
    	std::vector<Apple> basket;
    };
    
    void print(VectorWrapper<Fruit>& list){
    	// Hier braucht man natürlich noch andere Funktionen in ListWrapper
    	for(int i=0; i< list.size(); ++i){
    		list[i].print_name();
    	}
    }
    
    int main(){
    	AppleBasket apples;
    	BananaBasket bananas;
    	VectorWrapper<Fruit> apple_list = apples.get_list();
    	VectorWrapper<Fruit> banana_list = bananas.get_list();
    	print(apple_list);
    	print(banana_list);
    }
    

    apple
    apple
    apple
    banana
    banana

    Gruß,
    XSpille



  • Das weiss ich, aber es gibt trotzdem Fälle in denen man nicht drum herum kommt (soweit ich weiss).

    Ein Fall der mir grad im Kopf rum schwirrt:
    Man möchte einen Instant-Messenger entwickeln. Man bekommt über Sockets serialisierte Objekte gesendet, die kann man an Hand einer Factory wieder deserialisieren und whatever damit machen. Wenn man die abstrakte Message (bspw. Datei, Nachricht oder irgendwas) dann hat, muss trotzdem unterschieden werden an Hand des genauen Typs, wie diese weiterverarbeitet werden soll.



  • skNiNe schrieb:

    Das weiss ich, aber es gibt trotzdem Fälle in denen man nicht drum herum kommt (soweit ich weiss).

    Ein Fall der mir grad im Kopf rum schwirrt:
    Man möchte einen Instant-Messenger entwickeln. Man bekommt über Sockets serialisierte Objekte gesendet, die kann man an Hand einer Factory wieder deserialisieren und whatever damit machen. Wenn man die abstrakte Message (bspw. Datei, Nachricht oder irgendwas) dann hat, muss trotzdem unterschieden werden an Hand des genauen Typs, wie diese weiterverarbeitet werden soll.

    oder man ruft die virtuelle Methode verarbeite() auf.



  • Mh die Message soll tatsächlich selbst wissen wie sie verarbeitet wird? Ich finde es naheliegender wenn die Message nur ihre Daten beinhaltet und durch nen speziellen Handler verarbeitet wird (der evtl. auch die nötigen Referenzen auf andere Objekte hält die zur Verarbeitung nötig sind).



  • skNiNe schrieb:

    Mh die Message soll tatsächlich selbst wissen wie sie verarbeitet wird? Ich finde es naheliegender wenn die Message nur ihre Daten beinhaltet und durch nen speziellen Handler verarbeitet wird (der evtl. auch die nötigen Referenzen auf andere Objekte hält die zur Verarbeitung nötig sind).

    Mhmm, die Message kommt über eine Connection rein.

    msg->process(&connection);
    

    wäre schonmal generell möglich.
    Und in process kann sich die Message bestimmt von der Connection auch den Nick oder die Sitzung holen, den Server oder wasauchimmer. Vielleicht.
    Ich halte es für überlegenswert.



  • Also wch will meinen Gedankengang nicht als DEN besten darstellen. Aber ich kann mcih damit nicht anfreunden, wenn in einer Message (reines Datenobjekt) irgendwelche Funktionalität implementiert wird. Da würde mir der Bezug zur Realität fehlen (was ja mit im Fokus der OOP steht). Wenn ich Post bekomme, weiss der Brief auch nicht was er nun machen muss. Davon abgesehen finde ich das Konzept der selbstverwaltung der Messages unflexibel. Es müsste eine fette Schnittstelle existieren um die nötigen Referenzen auf die Messages zu setzen bzw. zu übergeben. Was ist wenn neue Messages dazu kommen und weitere bzw. andere Informationen zusätzlich brauchen? Schnittstellenänderungen und alles hin und her usw. ... Da finde ich es persönlich sauberer einmal in einer Factory per dynamic_cast den passenden Handler für die Message zu ermitteln und die Funktionalität von der Message sauber getrennt zu haben.

    + Höhere Erweiterbarkeit / Flexibilität
    + Sauberere Trennung

    - Typüberprüfung zur Laufzeit

    Da bin ich dann doch am überlegen was sinnvoller ist.

    PS: Wie gesagt, ich will nicht meine Lösung als die beste darstellen, sondern einfach die richtigerere Lösung diskutieren.



  • Och, ich stellte mir vor, daß die Factory den Stream liest und Messages aus dem Bytestrom baut. Das sind sozusagen über's Netz geschickte Funktionsaufrufe.
    Man kann auch erst nur Messages empfangen, reine Datenobjekte, und dann mit einer Factory daraus den benötigten Handler erzeugen.
    Das ist jetzt für mich gar kein großer ideologischer Unterschied.
    Ich weiß nicht, ob ich "Saubere Trennung" überhaupt haben will.



  • Um was für Messages handelt es sich da, wie sind die aufgebaut und wie sollen sie verarbeitet werden?



  • dot schrieb:

    Um was für Messages handelt es sich da, wie sind die aufgebaut und wie sollen sie verarbeitet werden?

    Seit 16 Jun 2011 23:08 welche, die brauchbar für einen Instant-Messenger sind.



  • Aus welchem Grund sollte man nicht sauber trennen? Ausseraus Performancegründen? Bin erstmal weg.



  • skNiNe schrieb:

    Aus welchem Grund sollte man nicht sauber trennen? Ausseraus Performancegründen? Bin erstmal weg.

    Weil Du anscheinend Daten und Funktionen voneinander trennst. Ist das nicht das genaue Gegenteil von objektorientierter Programmierung? Ich stelle mir vor, daß die Trennung am Ende vielleicht mehr Aufwand bedeutet. Ich verstehe nicht so ganz, was getrennt wird und wozu.



  • Explizit Typen unterscheiden muss man sehr selten. Der grösste Teil kann durch virtuelle Funktionen abgedeckt werden. Für den Rest lohnt sich manchmal ein Griff in die Trickkiste, um Techniken wie Dynamic Dispatch herauszuholen.

    Wichtig ist, dass man von if - else und switch wegkommt. Klar gibts einzelne Fälle wo das okay ist, besonders weil Alternativen zu umständlich sind. Das Problem ist: Manuelle Unterscheidungen skalieren sehr schlecht und erhöhen Abhängigkeiten. Mit dynamic_cast hast du üblicherweise keine höhere Flexibilität und Erweiterbarkeit, im Gegenteil. Du musst den ganzen Code an einer Stelle halten (alle abgeleiteten Klassen müssen dazu bekannt sein), die Reihenfolge der if - else -Statements ist entscheidend und damit eine Fehlerquelle, bei jeder abgeleiteten Klasse muss dran gedacht werden, einen neuen Fall einzuführen, etc.



  • XSpille schrieb:

    template<typename T>
    class InternalVectorWrapper{
    public:
    	virtual ~InternalVectorWrapper(){}
    	virtual InternalVectorWrapper<T>* copy() const = 0;
    	virtual T& operator[](unsigned int i) = 0;
    	virtual unsigned int size() = 0;
    };
    
    template<typename Type, typename TBase>
    class SpecificVectorWrapper : public InternalVectorWrapper<TBase>{
    public:
    	SpecificVectorWrapper(std::vector<Type>& list) : list(list){
    	}
    
    	virtual InternalVectorWrapper<TBase>* copy() const {
    		return new SpecificVectorWrapper<Type, TBase>(*this);
    	}
    	virtual TBase& operator[](unsigned int i){
    		return list[i];
    	};
    
    	virtual unsigned int size(){
    		return list.size();
    	}
    
    private:
    	std::vector<Type>& list;
    };
    
    template<typename TBase>
    class VectorWrapper{
    public:
    	template<typename T>
    	VectorWrapper(std::vector<T>& t) : ptr(new SpecificVectorWrapper<T, TBase>(t)){}
    	~VectorWrapper(){ delete ptr; }
    
    	VectorWrapper(const VectorWrapper<TBase>& w){
    		this->ptr = w.ptr->copy(); 
    	}
    
    	// Zuweisungsoperator implementieren...
    
    	TBase& operator[](unsigned int i){
    		return ptr->operator[](i);
    	}
    
    	unsigned int size(){
    		return ptr->size();
    	}
    
    protected:
    	InternalVectorWrapper<TBase>* ptr;
    };
    

    Ist das der Weg, den ihr gehen würdet, wenn ihr ein '? extends Foo' für dynamische Polymorphie verwirklichen wollen würdet oder wie würdet ihr das machen



  • Nachgefragt... schrieb:

    Ist das der Weg, den ihr gehen würdet, wenn ihr ein '? extends Foo' für dynamische Polymorphie verwirklichen wollen würdet oder wie würdet ihr das machen

    '? extends Foo' macht doch nur im Zusammenhang mit generics Sinn oder!? Und wie schon gesagt braucht man das in C++ nicht. templates sind eben keine generics...


Anmelden zum Antworten