Command Handler



  • Oder der Java-Ansatz

    struct Command
    {
      virtual void execute() = 0;
      virtual ~Command() {}
    };
    
    std::map<std::string, Command*> commands;
    

    Von dieser kann abgeleitet werden und durch Membervariablen fuer Parameter, Kontext etc. erweitert werden. http://www.parashift.com/c++-faq-lite/pointers-to-members.html#faq-33.13



  • knivil schrieb:

    Oder der Java-Ansatz

    struct Command
    {
      virtual void execute() = 0;
      virtual ~Command() {}
    };
    
    std::map<std::string, Command*> commands;
    

    Von dieser kann abgeleitet werden und durch Membervariablen fuer Parameter, Kontext etc. erweitert werden. http://www.parashift.com/c++-faq-lite/pointers-to-members.html#faq-33.13

    Das macht man in Java, weil es da keine Funktionszeiger bzw. so etwas wie Delegates aus C# gibt. Es gibt keinen Grund, das in C++ so zu machen.
    (Java hat im Gegensatz zu C++ eine Syntax für anonyme, lokale Klassen, wodurch der "Java-Ansatz" fast benutzbar wird.)
    Für C++ gibt es boost::function , was inzwischen als std::function in den Standard übernommen worden ist.



  • Ich würde das zum aktuellen Zeitpunkt das Ganze ungefähr wie folgt machen:

    Eine Klasse für die Abhandlung der Kommandos:
    - Ein privater Member ist eine map mit strings als key und Funktionszeiger als Elemente, bei denen es einen Parameter gibt, der ein vector (oder eine eigene verkettete Liste) ist, der Zeiger auf Objektinstanzen enthält (möglicherweise mit einem Smart Pointer Systems).
    - Einige Prozeduren / Funktionen für den Zugriff

    Ich habe aber nun folgende Befürchtung:

    -Das Ganze wird völlig konfus und durcheinander, dadurch bedingt, dass ich FktZeiger aus allen möglichen Bereichen des Programmes hinzufüge.
    -Die Kapselung könnte stark leiden.

    Dazu hätte ich gerne eine Meinung zu den folgendem Konstrukten:
    (Beispiele in C++)

    typedef unsigned int Adress;
    //---------------------------------------------------------------------------
    //Ein Zeiger auf einen beliebigen Typen mit Typnamen:
    struct DynTypePointer
    {
    	 Adress Adresse;
    	 char* Typ;
    	 DynTypePointer(Adress Adresse, const char* Typ) {
    		 this->Adresse = Adresse;
    		 strcpy(this->Typ,const_cast <char*> (Typ));
         }
    };
    //---------------------------------------------------------------------------
    

    Der Name ist Optional. Vielleicht zur Verifikation der Parametertypen.
    EDIT: Dazugehörig:

    //---------------------------------------------------------------------------
    template <typename Type>
    const DynTypePointer& CreateDynamicHandle(Type* Var) {
    	return DynTypePointer(reinterpret_cast <Adress> (Var),typeid(*Var));
    }
    //---------------------------------------------------------------------------
    template <typename Type>
    const Type& GetVarFromHandle(DynTypePointer& DTPointer) {
    	Type* TypePointer = reinterpret_cast <Type*> (DTPointer.Adresse);
    	return (strcmp(DTPointer.Typ,typeid(TypePointer).name())) ? (TypePointer) : (static_cast <Type*> (0));
    }
    //---------------------------------------------------------------------------
    

    Oder die folgende Idee zu einem Smart Pointer Konstrukt, dass seine Kopien kennt und sich erst löscht, wenn alle Klone tot sind:

    //Eine Einzelinstanz eines Objektes, dass sich löscht, wenn alle Kopien aus ihren Scopes verschwunden sind:
    template <class T>
    class SingleInstance {
    private:
    	SingleInstance<T> * const GetLastPointer(SingleInstance<T> * Compound);
    	SingleInstance<T> * const GetFirstPointer(SingleInstance<T> * Compound);
    
    	T* Object;
    	SingleInstance<T> *Parent;
    	SingleInstance<T> *Ancestor;
    
    	SingleInstance() {
    	} // not usable
    
    public:
    	SingleInstance(T* Obj) {
    		//if (sizeof(*Obj) == sizeof(void*))  KEINE ZEIGER
    		//warning!
    		this->Object = Obj;
    		this->Parent = 0;
    		this->Ancestor = 0;
    	}
    
    	SingleInstance(SingleInstance<T> * Par) {
    		SingleInstance<T> * Last = GetLastPointer(Par);
    		this->Parent = Last;
    		this->Ancestor = 0;
    		this->Object = Last->Get();
    		this->Parent->SetAncestor(this);
    	}
    
    	~SingleInstance() {
    		// Keine Väter oder Erben mehr?
    		if (Parent == 0 && Ancestor == 0) {
    			delete Object;
    		}
    		else // es gibt noch Verwandte
    		{
    			// Verwandschaften umverlegen:
    			if (Ancestor != 0)
    				Ancestor->SetParent(Parent);
    			if (Parent != 0)
    				Parent->SetAncestor(Ancestor);
    		}
    
    	}
    
    	// Väter GET SET
    	SingleInstance* GetParent() {
    		return this->Parent;
    	}
    
    	void SetParent(SingleInstance* NewParent) {
    		this->Parent = NewParent;
    	}
    
    	// Erben GET SET
    	SingleInstance* GetAncestor() {
    		return this->Ancestor;
    	}
    
    	void SetAncestor(SingleInstance* NewAncestor) {
    		this->Ancestor = NewAncestor;
    	}
    
    	T* Get() {
    		return Object;
    	}
    
    	SingleInstance* const operator[](unsigned int Index) {
    		SingleInstance* Current = GetFirstPointer(this);
    		for (DWORD i = 0; i < Index; i++) {
    			if (Current != 0)
    				Current = Current->Ancestor;
    			else
    				throw Exception("out of range");
    		}
    		return Current;
    	}
    
    	unsigned int InstanceCount() {
    		SingleInstance* Current = GetFirstPointer(this);
    		unsigned int Counter = 0;
    		while (Current != 0) {
    			Current = Current->Ancestor;
    			Counter++;
    		}
    		return Counter;
    	}
    };
    
    //Ersten in Liste suchen:
    template<typename T>
    SingleInstance<T> * const SingleInstance<T>::GetFirstPointer(SingleInstance<T> * Compound) {
    	if (Compound == 0) {
    		return 0;
    	}
    	else
    		do {
    			if (!Compound->GetParent())
    				break;
    			Compound = Compound->GetParent();
    		}
    		while (Compound != 0);
    
    	return Compound; // Cannot be NULL
    }
    
    //Letzten in Liste suchen:
    template<typename T>
    SingleInstance<T> * const SingleInstance<T>::GetLastPointer(SingleInstance<T> * Compound) {
    	if (Compound == 0) {
    		return 0;
    	}
    	else
    		do {
    			if (!Compound->GetAncestor())
    				break;
    			Compound = Compound->GetAncestor();
    		}
    		while (Compound != 0);
    
    	return Compound; // Cannot be NULL
    }
    

    Beides würde ich möglicherweise nutzen.



  • Tim06TR schrieb:

    Ich würde das zum aktuellen Zeitpunkt das Ganze ungefähr wie folgt machen:

    Eine Klasse für die Abhandlung der Kommandos:
    - Ein privater Member ist eine map mit strings als key und Funktionszeiger als Elemente, bei denen es einen Parameter gibt, der ein vector (oder eine eigene verkettete Liste) ist, der Zeiger auf Objektinstanzen enthält (möglicherweise mit einem Smart Pointer Systems).
    - Einige Prozeduren / Funktionen für den Zugriff

    Kannst du das noch einmal verständlich erklären? Vor allem: Was willst du erreichen? Wie wird man das ganze benutzen?

    Tim06TR schrieb:

    Dazu hätte ich gerne eine Meinung zu den folgendem Konstrukten:
    (Beispiele in C++)

    typedef unsigned int Adress; //@@ das Wort heißt address mit zwei d
    //---------------------------------------------------------------------------
    //Ein Zeiger auf einen beliebigen Typen mit Typnamen:
    struct DynTypePointer //@@ warum heißt das so? Ist doch gar kein "pointer"
    {
    	 Adress Adresse;
    	 char* Typ; //@@ warum nicht const char *?
    	 DynTypePointer(Adress Adresse, const char* Typ) {
    		 this->Adresse = Adresse;
    		 strcpy(this->Typ,const_cast <char*> (Typ)); //@@ das ist Quatsch, lern mal C oder C++
         }
    };
    //---------------------------------------------------------------------------
    

    Der Name ist Optional. Vielleicht zur Verifikation der Parametertypen.
    EDIT: Dazugehörig:

    //---------------------------------------------------------------------------
    template <typename Type>
    const DynTypePointer& //@@ du gibts eine Referenz auf ein temporäres Objekt zurück, macht man nicht und ist nicht standardkonform
    CreateDynamicHandle(Type* Var) {
    	return DynTypePointer(reinterpret_cast <Adress> (Var),typeid(*Var)); //@@ warum speichert du nicht gleich den Zeiger? Dieser Cast ist total unnötig und unportabel
    //@@ der Typ von typeid(..) ist nicht char *
    }
    //---------------------------------------------------------------------------
    template <typename Type>
    const Type& GetVarFromHandle(DynTypePointer& DTPointer) {
    	Type* TypePointer = reinterpret_cast <Type*> (DTPointer.Adresse);
    	return (strcmp(DTPointer.Typ,typeid(TypePointer).name())) ? (TypePointer) : (static_cast <Type*> (0)); //@@ der Rückgabetyp ist eine Referenz, aber du gibts einen Zeiger zurück?
    }
    //---------------------------------------------------------------------------
    

    Oder die folgende Idee zu einem Smart Pointer Konstrukt, dass seine Kopien kennt und sich erst löscht, wenn alle Klone tot sind:
    //@@ benutze existierende "smart pointer" aus Boost oder Std, bevor du irgendwelchen Blödsinn fabrizierst

    //Eine Einzelinstanz eines Objektes, dass sich löscht, wenn alle Kopien aus ihren Scopes verschwunden sind:
    

    Beides würde ich möglicherweise nutzen. //@@ ich nicht

    Hast du überhaupt mal versucht das zu kompilieren?



  • TyRoXx schrieb:

    Das macht man in Java, weil es da keine Funktionszeiger bzw. so etwas wie Delegates aus C# gibt. Es gibt keinen Grund, das in C++ so zu machen.

    Bullshit! Auf Boost verzichte ich gern. Funktion(szeiger) haben eine Feste Parameteranzahl und koennen keinen Kontext speichern. Ich habe hier sowas wie "poor mans closure".

    @Tim06TR: Was soll das ganze Gedoens. Erst ein riesigen Templatesalat zubereiten und dann noch mit strcmp arbeiten. Das passt nicht zusammen.



  • Tim06TR schrieb:

    typedef unsigned int Adress;
    //---------------------------------------------------------------------------
    //Ein Zeiger auf einen beliebigen Typen mit Typnamen:
    struct DynTypePointer
    {
    	 Adress Adresse;
    	 char* Typ;
    	 DynTypePointer(Adress Adresse, const char* Typ) {
    		 this->Adresse = Adresse;
    		 strcpy(this->Typ,const_cast <char*> (Typ));
         }
    };
    //---------------------------------------------------------------------------
    

    Wenn Du sowas vor hast, warum nicht type_info benutzen?

    Tim06TR schrieb:

    Oder die folgende Idee zu einem Smart Pointer Konstrukt, dass seine Kopien kennt und sich erst löscht, wenn alle Klone tot sind:

    Ok, das sieht man gelegentlich. Meistens, weil Leute traurig sind, daß C++ doch anders ist als Java. Es kann aber auch sinnvoll sein.
    Die Smart-Pointers können refcounting betreiben oder sich in einer Liste gegenseitig kennen. Der letzte macht das List aus.
    Und Du hast Dich mal für die Liste entschieden. Ok. Die hat Vor- und Nachteile (und wird meistens unterschätzt). Ich hoffe, ich habe das so recht verstanden. Weil Du von Eltern und Erben erzählst statt von gleichberechtigten Nachbarn.
    Falls es die Nachbarversion sein soll: Smart-Sachen und Listen-Sachen sind zwei Klassen. Wiedermal eine intrusive Liste. 🤡 Da sie niemals leer ist, können wir uns einen doppelt verketteten niemals leeren Ring gönnen, dann fliegen die ganzen Vergleiche gegen 0 heraus. Und der op[], das Holen des ersten oder letzten Elements gehören gar nicht rein.



  • knivil schrieb:

    TyRoXx schrieb:

    Das macht man in Java, weil es da keine Funktionszeiger bzw. so etwas wie Delegates aus C# gibt. Es gibt keinen Grund, das in C++ so zu machen.

    Bullshit! Auf Boost verzichte ich gern. Funktion(szeiger) haben eine Feste Parameteranzahl und koennen keinen Kontext speichern. Ich habe hier sowas wie "poor mans closure".

    Mit Betonung auf poor. Warum soll dein low-level-Ansatz besser sein als function plus bind ?



  • TyRoXx schrieb:

    Mit Betonung auf poor. Warum soll dein low-level-Ansatz besser sein als function plus bind ?

    1.) Leg mir keine Worte in den Mund.
    2.) Er ist simpel.



  • knivil schrieb:

    2.) Er ist simpel.

    Hat aber gegenüber std::function, std::bind gewisse Einschränkungen die ich durchaus berücksichtigen würde. Entgegen eines Funktionszeigers kann std::bind eben auch:
    a) Memberfunktionen/Methoden aufrufen.
    b) Man kann Argumente fest binden, sofern nötig.
    c) Man kann Funktionssignaturen in bestimmten Grenzen anpassen (z.B. Reihenfolgen) ohne den Code an mehreren Stellen ändern zu müssen.

    Teure Smartpointer (wie shared_ptr) würde ich aber auch nur einführen, wenn im Szenario unbedingt nötig, und nicht früher...



  • Autsch @ mein uraltes DynTypeHandle Konstrukt.
    Ich hätte nochmal drüberschauen sollen.

    Ich hab das nochmal etwas abgeändert.

    //---------------------------------------------------------------------------
    typedef void* Handle;
    
    // Does not handle the ressource at all!
    struct DynType {
    	Handle Instance;
    	std::type_info* InstanceType;
    
    	~DynType() {
    		delete InstanceType;
    	}
    };
    //---------------------------------------------------------------------------
    template <class T>
    DynType CreateDynType(T* Object) {
    	DynType Dyn;
    	Dyn.Instance = static_cast <Handle> (Object);
    	Dyn.InstanceType = new std::type_info(T);
    	return Dyn;
    }
    //---------------------------------------------------------------------------
    template <class T>
    T GetDynType(const DynType& Dyn) {
    	if (*Dyn.InstanceType == typeid(T))
    		return static_cast <T*> (Dyn.Instance);
    	else
    		return static_cast <T*> (0);
    }
    //---------------------------------------------------------------------------
    

    Sowas wie ein Variant wäre echt mal nett.
    Ich weiß, dass das oben immer noch nicht sehr schön ist, aber füttert mich mal mit ein bisschen Alternativen.

    Die Parameter der Prozeduren sind ja nicht immer gleich, noch sind es gleich viele. Auf Parameter verzichten ist nicht möglich.



  • Sag uns doch mal in Worten was du wirklich machen willst, für mich sieht dies alles eher sehr unausgegoren und unverständlich aus.



  • Ok nochmal:

    1. Command Handler (Console / Server)
    2. Ein große Liste mit Befehlen und den dazugehörigen Prozeduren
    a. Die Prozeduren haben n Parameter
    b. Die Parameter davon haben differente Typen

    - Die Befehle sind zur Steuerung des gesammten Programms, brauchen deshalb auch unterschiedliche "Bekanntschaften". Ich will kein Gottobjekt, wenn möglich.

    Mögliche weitere Dinge wären:
    - Eine Warteschlange für hereinkommende Befehle. Der Listener in einem Thread, der die Befehle auffängt und zur Liste hinzufügt.

    Das ist eigentlich alles kein Problem, könnte loslegen, wenn ich wollte. Aber ob ich hinterher noch alles durchblicken kann ist ne andere Frage & ob ich nach dem 100000ten Befehl nicht eine Sekunde warten muss, bis der den überhaupt gefunden hat.
    Zudem will ich keine Kapselung aufgeben oder, anders ausgedrückt, einen imperativen Code basteln. Basteln trifft es bei unüberlegtem Programmdesign sowieso immer ganz gut.



  • Tim06TR schrieb:

    Ok nochmal:

    Nein nicht nochmal, wir wollen eine Erklärung von dem was du dir vorstellst, und dem Sinn dahinter. Mit wenigen Stichworten können wir nur raten.

    Beginnen wir einfach mal:

    Tim06TR schrieb:

    Command Handler (Console / Server)

    Command Handler ist vermutlich weniger das Verständnisproblem (Außer vielleicht wenn man deinen Code dazu nimmt), recht schwammig ist dabei aber das was du im Zusammenhand mit Console/Server darein interpretieren magst.

    Tim06TR schrieb:

    2. Ein große Liste mit Befehlen und den dazugehörigen Prozeduren
    a. Die Prozeduren haben n Parameter
    b. Die Parameter davon haben differente Typen

    2 an sich ist noch okay, aber wie stellst du dir a&b vor, und mit welchen Sinn, und vor allem: Wann stehen die Parameter genau fest? Eine Befehlsliste macht imho wenig Sinn wenn man dann noch raten muss wie man die Parameter füllt. C++ ist keine dynamische Sprache.

    Ungeachtet davon was ich davon halten würde wäre eine Variante natürlich die Signatur deiner Befehle immer in String-Wertepaaren anzugeben (natürlich fehlt jegliche Typprüfung etc.). z.b.:

    std::map<string, string> <funktion>(std::map<string, string> parameter);

    Aber wie gesagt, der Sinn wäre eher fraglich.

    Wenn ich eine Liste von Befehlen halte, würde ich erwarten das die beim Aufruf ihre Sachen machen, ohne irgendwelche Parameter. Die Befehle wiederum haben aber natürlich wieder selbst die Möglichkeit sich bestimmte Informationen aus dem System zu holen (Auch wenn Singletons nicht gerne gesehen werden: z.B. aus einer Konfigurationsklasse).

    Wenn wiederum die Parameter beim Aufruf festgelegt werden können, oder zumindest gesagt werden kann, mit welcher Funktion beim Aufruf die Informationen geholt werden können, sind wir wieder bei std::function und std::bind.

    Beispiel (ungetestet, und grad bei den verschachtelten unsicher):

    class A
    {
        void foo1(int a) {};
        void foo2(int b, int c) {};
        static void foo3() {};
    };
    
    void foo4() {}
    void foo5(int a) {}
    
    int getFoo6() { return 1 };
    
    int main()
    {
      A a;
    
      std::map<string, std::function<void (void)>> befehlsliste;
      befehlsliste["foo1"] = std::bind(&A::foo1, &a, 4);
      befehlsliste["foo2"] = std::bind(&A::foo2, &a, 2, boost::bind(&getFoo6));
      befehlsliste["foo3"] = std::bind(&A::foo3);
      befehlsliste["foo4"] = std::bind(&A::foo4);
      befehlsliste["foo5"] = std::bind(&A::foo5, boost::bind(&getFoo6));
    
      // Aufruf:
      befehlsliste["foo3"]();
    }
    


  • asc schrieb:

    Wenn ich eine Liste von Befehlen halte, würde ich erwarten das die beim Aufruf ihre Sachen machen, ohne irgendwelche Parameter.

    Also Aufträge (Jobs)(die sicher ihre Parameter kennen), und Befehle (Commands)(die z.B. in Basic Parameter brauchen).

    Ich denke, es sollen Jobs über das Netz oder nur über die Zeit verschickt werden.



  • Meine Codeschnipsel waren nicht mit dem Problem an sich verbunden, da wollte ich noch hinkommen -.-.

    &Mir ist gerade eingefallen, dass die Parameter ohnehin nur Strings sind.. vor allem weiß der Aufrufer doch gar nicht die Parametertypen...



  • Hier sagst Du irgendwas...

    knivil schrieb:

    ...Bullshit! Auf Boost verzichte ich gern. Funktion(szeiger) haben eine Feste Parameteranzahl und koennen keinen Kontext speichern. Ich habe hier sowas wie "poor mans closure".

    Und hier revidierst Du Dich selbst und gibst als Alternative Lösung etwas funktinal äquivalentes und technische schlechteres, selbstgefrickeltes an:

    knivil schrieb:

    Oder der Java-Ansatz

    struct Command
    {
      virtual void execute() = 0;
      virtual ~Command() {}
    };
    
    std::map<std::string, Command*> commands;
    

    Von dieser kann abgeleitet werden und durch Membervariablen fuer Parameter, Kontext etc. erweitert werden....

    Also was denn nun?



  • Das Bullshit bezog sich auf: Es gibt keinen Grund, das in C++ so zu machen. Als ob das jemals jemanden von irgendwas abgehalten haette. Aber da weder Umfang, Bezug zur Anwendung oder Anforderungen genannt werden, waehle ich das aus meiner Sicht einfachste.

    funktinal äquivalentes und technische schlechteres, selbstgefrickeltes

    Das ist ein einfaches Interface mit 3 Zeilen Code. So viele (negative) Eigenschaften kann es gar nicht besitzen.



  • Das Interface selbst ist ja nicht böse, die Verwendung ist halt umständlich.
    Wobei man natürlich ganz einfach ein Funktions-Template basteln könnte, das dann beliebige Funktoren in das Interface einwickelt.

    Je mehr man allerdings in diese Richtung geht, desto ähnlicher wird es zu std::function.

    Und irgendwann sollte man sich dann fragen: wieso nicht gleich std::function nehmen.

    Wobei ich deinen Standpunkt durchaus verstehe. Speziell im Interface von Libs kann es sinn machen selbst zu backen, Standard halt nur Standard ist, aber nicht heisst dass es überall vorhanden ist oder gar gleich funktionieren würde.


Anmelden zum Antworten