Delegator: Funktionsargumente, relationale Operatoren



  • Hallo,

    ich implementiere gerade ein Delegator-Template.

    // abstraktes interface für funktoren
    template <typename ReturnType>
    class Functor
    {
    	public :
    		typedef Core::SmartPtr<Functor<ReturnType> > Ptr;
    		virtual ~Functor() { }
    		virtual ReturnType operator()() const = 0;
    };
    
    // callbacks auf statische funktionen
    template <typename ReturnType>
    class FunctionBinder : public Functor<ReturnType>
    {
    	typedef ReturnType(*Callback)();
    	public :
    		FunctionBinder(Callback cb) : 
    			mCallback(cb) 
    		{ }		
    		ReturnType operator()() const { return mCallback(); }
    	private :
    		Callback mCallback;
    };
    
    // callbacks auf member-funktionen
    template <typename Class, typename ReturnType>
    class MethodBinder : public Functor<ReturnType>
    {
    	typedef ReturnType(Class::*Callback)();
    	public :	
    		MethodBinder(Class *instance, Callback cb) : 
    			mInstance(instance), 
    			mCallback(cb) 
    		{ }		
    		ReturnType operator()() const { return (mInstance->*mCallback)(); }
    	private :
    		Class *mInstance;
    		Callback mCallback;		
    };
    
    // der eigentliche delegator
    template <typename ReturnType>
    class Delegator
    {
    	typedef ReturnType(*FCallback)();
    	public :
    		// konstruktor für statische callbacks
    		Delegator(FCallback cb)
    		{
    			mFunctor = new FunctionBinder<ReturnType>(cb);
    		}
    
    		// konstruktor für member-callbacks
    		template <typename Class>
    		Delegator(Class *instance, ReturnType(Class::*cb)())
    		{
    			mFunctor = new MethodBinder<Class, ReturnType>(instance, cb);
    		}
    
    		ReturnType operator()() const
    		{
    			return (*mFunctor)();
    		}
    	private :
    		typename Functor<ReturnType>::Ptr mFunctor;
    };
    

    Damit kann ich jetzt also Callbacks auf beliebige Funktionen und Methoden binden. Funktioniert soweit problemlos. Nur bei zwei Features weiß ich nicht so recht, wie ich sie implementieren soll.

    Zum einen wären das die relationalen Operatoren, zumindest auf Gleichheit müssen sie prüfbar sein. Dabei bedeutet Gleichheit, dass auf die selbe Callback-Funktion gezeigt wird. Da Functor<> aber von Callbacks gar nichts weiß, fehlt mir der Durchblick, wo und wie ich die Operatoren hinzufügen soll.
    Ich habe mir überlegt, einen void-Pointer in Functor<> aufzunehmen, dem ich in den Spezialisierungen die Callback-Funktion zuweise. Die könnte ich dann einfach vergleichen. Mein Verständnis von C++ sagt mir aber, dass void-Pointer auf Memberfunktionen alles andere als eindeutig sind.

    Der zweite Knackpunkt sind Argumente. Diese Implementierung kann noch keine Argumente an die Callback-Funktionen weitergeben, das habe ich bewusst erst mal außen vor gelassen. Die beste Lösung, die mir bisher eingefallen ist, wären mehrere Kopien aller obigen Templates mit jeweils einem Argument mehr, also

    template <typename ReturnType>
    class Functor0
    [...]
    
    template <typename ReturnType, typename Arg0>
    class Functor1
    [...]
    
    template <typename ReturnType, typename Arg0, typename Arg1>
    class Functor2
    [...]
    

    ...und so weiter, auch für die Spezialisierungen. Variadic Templates wären hier wohl das Richtige, wenn sie denn schon überall verfügbar wären.

    Gibt es elegantere Lösungen, die mir nicht eingefallen sind?



  • @Vergleiche: Wenn du einen Vergleich einbauen willst, dann vermutlich als virtuelle Methode in den Funktor<>-Klassen:

    //Ausschnitt:
    template<typename ret>
    class Functor
    {
    public:
      ...
      virtual bool compare(const Functor<ret>* other)=0;
    };
    
    template<typename ret>
    class FunctionBinder : public Functor<ret>
    {
    public:
      ...
      virtual bool compare(const Functor<ret>* other)
      {
        FunctionBinder<ret>* pother = dynamic_cast<FunctionBinder<ret>*>(other);
        //wenn du zwei verschiedene Functor-Klassen vergleichen willst, liefert dynamic_cast<> NULL und damit erhältst du automatisch false
        return (pother!=NULL) && (pother->mCallback==this->mCallback);
      }
    };
    
    template<typename ret>
    class Delegator
    {
    public:
      ...
      bool operator==(const Delegator<ret>& other)
      { return mFunctor->compare(other.mFunctor);}
      bool operator!=(const Delegator<ret>& other)
      { return !mFunctor->compare(other.mFunctor);}
    };
    

    Zur Übergabe von Argmuneten fällt mir auch nichts ein.



  • Das funktioniert prinzipiell mal, danke. Ich sollte wohl meine Casting-Paranoia etwas runterfahren, damit mir sowas selbst einfällt 🙂

    Problematisch ist nur, dass ich auf RTTI verzichten möchte und dynamic_cast deshalb nicht in Frage kommt. Es lässt sich aber auch mit static_cast implementieren, wenn ich mich um die Typsicherheit selbst kümmere.



  • @pock
    Schau dir einfach mal Loki::Functor (http://loki-lib.sourceforge.net/) bzw. boost::function (http://www.boost.org/doc/html/function.html) an, da findest du jeweils Ansätze für die Unterstützung von Argumenten. Letztlich läuft jeder Ansatz auf Code-Duplikation hinaus.

    Btw: Dein Ansatz funktioniert zwar, ist aber nicht sonderlich effizient (vorallem was Code-Größe angeht). Falls es sich nicht nur um ein Lernprojekt handelt, würde ich dir zu einer fertigen Lib raten. boost::function ist sehr mächtig und ausgereift. Loki::Functor ist dafür etwas performanter. FastDelegate ist auf Geschwindigkeit optimiert, dafür aber weder hübsch noch wirklich portabel.



  • pock schrieb:

    Problematisch ist nur, dass ich auf RTTI verzichten möchte und dynamic_cast deshalb nicht in Frage kommt. Es lässt sich aber auch mit static_cast implementieren, wenn ich mich um die Typsicherheit selbst kümmere.

    Du arbeitest mit virtuellen Methoden - und dann willst du auf RTTI verzichten? Wozu soll das etwas bringen?

    (wenn du auf dynamic_cast verzichten willst, mußt du wirklich diese Typ-Überprüfung selber durchführen:

    bool FunctionBinder<ret>::compare(const Functor<ret>* other)
    {
      //gettype() ist eine virtuelle Methode, die eine je nach Typ eindeutigen ID liefert
      return (other->gettype()==ID_FUNC)&&(static_cast<FunctionBinder<ret>*>(other)->mFunctor==this->mFunctor);
    }
    


  • Danke für die Tipps.

    RTTI möchte ich deshalb nicht nutzen, weil es meiner Meinung nach schlechten Programmierstil fördert. Es mag Projekte geben, die den Einsatz von RTTI rechtfertigen, aber generell denke ich dass Typinformationen zur Laufzeit eher schädlich als nützlich sind und zu abenteuerlichen Konstruktionen führen können. Wenn man einen Hammer hat, sieht alles nach Nagel aus. Zudem soll der Code von Leuten genutzt werden, die nicht unbedingt C++-Kenner sind. Denen will ich lieber mal so wenig Möglichkeiten wie möglich bieten, sich in den Fuß zu schießen. Wenn die RTTI wollen, bitte. Dann ist es aber ihr Problem.

    Abhängigkeiten zu Dritt-Bibliotheken will und muss ich so gering wie möglich halten, deshalb kein boost etc. Als Inspirationsquelle natürlich gern.
    Ob mein Ansatz für den geplanten Einsatzzweck effizient genug ist, muss sich noch rausstellen.



  • pock schrieb:

    RTTI möchte ich deshalb nicht nutzen, weil es meiner Meinung nach schlechten Programmierstil fördert. Es mag Projekte geben, die den Einsatz von RTTI rechtfertigen, aber generell denke ich dass Typinformationen zur Laufzeit eher schädlich als nützlich sind und zu abenteuerlichen Konstruktionen führen können. Wenn man einen Hammer hat, sieht alles nach Nagel aus. Zudem soll der Code von Leuten genutzt werden, die nicht unbedingt C++-Kenner sind. Denen will ich lieber mal so wenig Möglichkeiten wie möglich bieten, sich in den Fuß zu schießen. Wenn die RTTI wollen, bitte. Dann ist es aber ihr Problem.

    Für so jemanden ist aber dynamic_cast bestimmt sicherer als jeder Versuch, RTTI von Hand nachzuahmen. Die einzige Alternative zu dynamic_cast<> und der dc-Imitation über gettype()+static-cast wäre vermutlich Double Dispatching, aber das ist wahrscheinlich noch komplexer.


Anmelden zum Antworten