Callback Problem



  • Hallo,

    Ich möchte eine Klasse schreiben, die als Basisklasse für andere Klassen dienen kann und für diese dann einen Callback-Mechanismus zur Verfügung stellt.

    #include <list>
    #include <iostream>
    #include <string>
    
    class CallbackVerwaltung
    {
    	class CallbackTop
    	{
    	public:
    		virtual void operator()(int) = 0;
    	};
    
    	template <class T>
    	class Callback : public CallbackTop
    	{
    		T* _object;
    		typedef void (T::*function)(int);
    		function _F;
    	public:
    		Callback(T* object, function f) : _object(object), _F(f) {}
    
    		virtual void operator()(int n)
    		{
    			(_object->*_F)(n);
    		}
    	};
    
    private:
    	std::list<CallbackTop*>* _callbacks;
    public:
    	CallbackVerwaltung()
    	{
    		_callbacks = new std::list<CallbackTop*>();
    	}
    
    	virtual ~CallbackVerwaltung()
    	{
    		typedef std::list<CallbackTop*>::iterator iter;
    		for (iter i = _callbacks->begin(); i != _callbacks->end(); ++i)
    		{
    			delete (*i);
    		}
    
    		delete _callbacks;
    	}
    
    	template <class T>
    	void addCallback(T* _object, void (T::*f)(int))
    	{
    		_callbacks->push_back(new Callback<T>(_object,f));
    	}
    
    	void informCallbacks(int n)
    	{
    		typedef std::list<CallbackTop*>::iterator iter;
    		for (iter i = _callbacks->begin(); i != _callbacks->end(); ++i)
    		{
    			(*(*i))(n);
    		}
    	}
    };
    
    class test1 : public CallbackVerwaltung
    {
    private:
    	std::string _name;
    public:
    	test1(const std::string& name) : _name(name) {}
    
    	void _test1(int n) { std::cout << "test1::_test1 object = \"" << _name << "\"with n = " << n << "\n"; }
    	void _test2(int n) { std::cout << "test1::_test2 object = \"" << _name << "\"with n = " << n << "\n"; }
    };
    
    class test2
    {
    private:
    	std::string _name;
    public:
    	test2(const std::string& name) : _name(name) {}
    
    	void _test1(int n) { std::cout << "test2::_test1 object = \"" << _name << "\"with n = " << n << "\n"; }
    };
    
    int main(int, char**)
    {
    	test1* t1_1 = new test1("t1_1");
    	test1* t1_2 = new test1("t1_2");
    	test1* t1_3 = new test1("t1_3");
    	test2* t2_1 = new test2("t2_1");
    
    	t1_1->addCallback(t1_2, &test1::_test1);
    	t1_1->addCallback(t1_2, &test1::_test2);
    	t1_1->addCallback(t1_3, &test1::_test2);
    	t1_1->addCallback(t2_1, &test2::_test1);
    
    	t1_1->addCallback(t1_2, &test1::_test1); 
    	// <-- soll nicht mehr hinzugefügt werden da schon vorhanden
    
    	t1_1->informCallbacks(42);
    
    	std::system("PAUSE");
    }
    

    Soweit klappt auch alles gut und es läuft wunderbar.

    Ich möchte jetzt jedoch überrpüfen, bevor ich einen Callback hinzufüge, ob dieser nicht schon vorhanden ist. (Wäre zwar auch machbar, wenn ich das nicht hinbekäme, jedoch benötige ich die Überprüfung auch, um den Callback zu entfernen)

    Scheitern tue ich aktuell jedoch daran, dass ich nicht weiß, wie ich einen Vergleich einbauen soll, der überprüft, ob der Callback schon vorhanden ist.

    Mein 2ter Versuch bisher.

    class CallbackTop
    	{
    	public:
      	// ...
    		virtual bool vorhanden(CallbackTop*) = 0;
    	};
    
    	template <class T>
    	class Callback : public CallbackTop
    	{
    // ...
    		virtual bool vorhanden(CallbackTop* cpt)
    		{
    // Hier komme ich nicht weiter
    		}
    	};
    

    Der erste Versuch bestand aus der Brechstangenmethode: Da template-Funktionen nicht virtuell sein können, hab ich einfach versucht, mit reinterpret_cast<int> die beiden argumete in ints zu verwandeln, aber das hat er auch mit einem Fehler quitiert.

    Wäre dankbar, wenn mir wer helfen könnte, das Problem zu lösen. (Auch ein anderer Ansatz wäre möglich, solange es möglich ist, verschiedene Objekte und Funktionen auf diese Objekte als Callback zu speichern)

    Mit freundlichen Grüßen
    Thorondir



  • Hy

    Bin mir nicht mehr ganz sicher wie ich es damals gemacht habe, aber ich glaub es war dieser Weg:

    template <class T> 
      class Callback : public CallbackTop 
      { 
         bool operator==( Callback& obj ) {
           return (obj._object == _object) && (obj._F == _F); 
         }
      };
    

    Und bei der Funktion zu einfüugen dann ca. so erweitern:

    void addCallback(T* _object, void (T::*f)(int)) {
      Callback* obj = new Callback(_object, f);
      for( std::list<CallbackTop*>::iterator iter = _callbacks->begin(); iter != _callbacks->end(); iter++ ) {
        if( *iter == *obj ) {
          // Existiert schon
          return;
        }
      }
      _callbacks->push_back(new Callback<T>(_object,f));
    }
    

    Dann solltest du vllt nur noch deine addCallback mit nen Rückgabewert versehen damit der Aufrufer auch weiß ob es funktioniert hat oder nicht.

    Mfg Marco



  • Deine Lösung hat leider auch nicht funktioniert.

    Er findet dann keine passende Funktion. Wenn ich einen Prototypen in CallbackTop einfüge, dann weiß er nicht, welche Funktion er nehmen soll.

    Habe es jetzt mit Dynamic-Cast gemacht:

    template <class T>
    	bool addCallback(T* _object, void (T::*f)(int))
    	{
    		typedef std::list<CallbackTop*>::iterator iter;
    		for (iter i = _callbacks->begin(); i != _callbacks->end(); ++i)
    		{
    			Callback<T>* casted = dynamic_cast<Callback<T>* >(*i);
    			if (casted)
    			{
    				if (casted->equals(_object, f))
    					return false;
    			}
    		}
    
    		_callbacks->push_back(new Callback<T>(_object,f));
    
    		return true;
    	}
    

    Das funktioniert auch soweit. - Sollte jemand eine Möglichkeit ohne Cast kennen, würde die mich interessieren. (Casts mag ich nicht so, aber ich befürchte, hier geht es nicht ohne, da das vergleichen nur möglich ist, wenn man den Template-Parameter des Objekts kennt-- und den kennt man halt nur zur Laufzeit. Wegen Template scheiden ja auch virtuelle Funktionen aus.)

    Jetzt hätte ich nur noch eine Frage: dynamic_cast liefert definitiv immer 0, wenn der Cast fehlschlägt? Oder ist das implementierungsabhängig?



  • thorondir schrieb:

    ...

    Er findet dann keine passende Funktion. Wenn ich einen Prototypen in CallbackTop einfüge, dann weiß er nicht, welche Funktion er nehmen soll.

    ....

    Wobei mir fällt grad was ein, und zwar wenn du an deiner Basis-Klasse die Funktion equal so "virtual bool equal( CallbackTop &obj) = 0" definierst sollte die passende Version der abgeleiteten Klasse aufgerufen werden. Und du müsstest nicht casten.
    Ich versteh aber nur noch nicht für was die Basis-Klasse benötigst. Würds nicht auch ohne der gehen?

    thorondir schrieb:

    ...

    Jetzt hätte ich nur noch eine Frage: dynamic_cast liefert definitiv immer 0, wenn der Cast fehlschlägt? Oder ist das implementierungsabhängig?

    ja

    Mfg marco



  • Marc-O schrieb:

    thorondir schrieb:

    ...

    Er findet dann keine passende Funktion. Wenn ich einen Prototypen in CallbackTop einfüge, dann weiß er nicht, welche Funktion er nehmen soll.

    ....

    Wobei mir fällt grad was ein, und zwar wenn du an deiner Basis-Klasse die Funktion equal so "virtual bool equal( CallbackTop &obj) = 0" definierst sollte die passende Version der abgeleiteten Klasse aufgerufen werden. Und du müsstest nicht casten.

    Das habe ich probiert, Problem ist aber, dass man dann aber die Funktion in Callback als Template definieren müsste und template und virtuell geht leider nicht.

    Marc-O schrieb:

    Ich versteh aber nur noch nicht für was die Basis-Klasse benötigst. Würds nicht auch ohne der gehen?

    Ich speicher ja die Callbacks in einer Liste. Aber da ich mehr als eine Klasse habe, die sich für Callbacks interessiert, geht etwas wie

    std::list<Callback<TYP>* >* _callbacks;
    

    nicht - es sei denn ich lasse den Typ-Parameter weg, aber dann könnte ich auch direkt mit void* Zeigern hantieren.

    Außerdem stürzt Visual-Studio dann beim Kompilieren ab... (Im destruktor ...)

    Marc-O schrieb:

    thorondir schrieb:

    ...

    Jetzt hätte ich nur noch eine Frage: dynamic_cast liefert definitiv immer 0, wenn der Cast fehlschlägt? Oder ist das implementierungsabhängig?

    ja

    Mfg marco

    Danke



  • Okay das mit der Basisklasse ist klar sobald es mehrer werden

    thorondir schrieb:

    Marc-O schrieb:

    thorondir schrieb:

    ...

    Er findet dann keine passende Funktion. Wenn ich einen Prototypen in CallbackTop einfüge, dann weiß er nicht, welche Funktion er nehmen soll.

    ....

    Wobei mir fällt grad was ein, und zwar wenn du an deiner Basis-Klasse die Funktion equal so "virtual bool equal( CallbackTop &obj) = 0" definierst sollte die passende Version der abgeleiteten Klasse aufgerufen werden. Und du müsstest nicht casten.

    Das habe ich probiert, Problem ist aber, dass man dann aber die Funktion in Callback als Template definieren müsste und template und virtuell geht leider nicht.

    ...

    Ich versteh nicht auf welches Problem du mit Templates und virtuell kommst, hier mal ein Konkretes Beispiel, aber etwas gekürzt, damit du verstehst was ich meine. Und dies funktioniert, ist nämlich frisch aus dem Compiler zurückgekehrt ;-):

    #include <iostream>
    
    #include "cb.hpp"
    
    using namespace std;
    
    class CallbackTop {
       public:
       virtual bool equal( CallbackTop *obj ) = 0;
    };
    
    template<class T>
    class Callback : public CallbackTop {
       public:
       Callback(T val) : _object( val ) {  }
       bool equal( CallbackTop *obj ) {
          Callback<T> *obj2 = dynamic_cast<Callback<T>*>( obj );
          if( obj2 ) {
             if( obj2->_object == this->_object ) {
                return true;
             }
          }
          return false;
       }
       private:
          T _object;
    };
    
    int main()
    {
        cout << "Hello world!" << endl;
    
        CallbackTop *obj1 = new Callback<int>( 1 );
        CallbackTop *obj2 = new Callback<int>( 2 );
        CallbackTop *obj3 = new Callback<int>( 1 );
    
        if( !obj1->equal( obj2 ) )
          cout << "obj1 not equal obj2" << endl;
    
        if( obj1->equal( obj3 ) )
          cout << "obj1 equal obj3" << endl;
        return 0;
    }
    

    Ausgabe:

    Hello world!
    obj1 not equal obj2
    obj1 equal ob3
    

    Ich hoffe es war jetzt verständlich. Und wenn nicht dann glaub ich reden wir aneinander vorbei. 😉

    Mfg marco



  • Marc-O schrieb:

    thorondir schrieb:

    Marc-O schrieb:

    thorondir schrieb:

    ...

    Er findet dann keine passende Funktion. Wenn ich einen Prototypen in CallbackTop einfüge, dann weiß er nicht, welche Funktion er nehmen soll.

    ....

    Wobei mir fällt grad was ein, und zwar wenn du an deiner Basis-Klasse die Funktion equal so "virtual bool equal( CallbackTop &obj) = 0" definierst sollte die passende Version der abgeleiteten Klasse aufgerufen werden. Und du müsstest nicht casten.

    Das habe ich probiert, Problem ist aber, dass man dann aber die Funktion in Callback als Template definieren müsste und template und virtuell geht leider nicht.

    ...

    Ich versteh nicht auf welches Problem du mit Templates und virtuell kommst, hier mal ein Konkretes Beispiel, aber etwas gekürzt, damit du verstehst was ich meine. Und dies funktioniert, ist nämlich frisch aus dem Compiler zurückgekehrt ;-):

    #include <iostream>
    
    #include "cb.hpp"
    
    using namespace std;
    
    class CallbackTop {
       public:
       virtual bool equal( CallbackTop *obj ) = 0;
    };
    
    template<class T>
    class Callback : public CallbackTop {
       public:
       Callback(T val) : _object( val ) {  }
       bool equal( CallbackTop *obj ) {
          Callback<T> *obj2 = dynamic_cast<Callback<T>*>( obj );
          if( obj2 ) {
             if( obj2->_object == this->_object ) {
                return true;
             }
          }
          return false;
       }
       private:
          T _object;
    };
    
    int main()
    {
        cout << "Hello world!" << endl;
    
        CallbackTop *obj1 = new Callback<int>( 1 );
        CallbackTop *obj2 = new Callback<int>( 2 );
        CallbackTop *obj3 = new Callback<int>( 1 );
    
        if( !obj1->equal( obj2 ) )
          cout << "obj1 not equal obj2" << endl;
    
        if( obj1->equal( obj3 ) )
          cout << "obj1 equal obj3" << endl;
        return 0;
    }
    

    Ausgabe:

    Hello world!
    obj1 not equal obj2
    obj1 equal ob3
    

    Ich hoffe es war jetzt verständlich. Und wenn nicht dann glaub ich reden wir aneinander vorbei. 😉

    Mfg marco

    Dann hab ich mich wohl etwas missverständlich ausgedrückt, bzw etwas falsch verstanden. Ich dachte, du meinst es ginge ohne dynamic_cast, etwa in der Form (hab den Code, den ich probiert habe, nicht mehr):

    class CallbackTop {
       public:
       virtual bool equal( CallbackTop *obj ) = 0;
    };
    
    template<class T>
    class Callback : public CallbackTop {
       public:
    
       bool equal( Callback<T> *obj ) {
       //...
       }
    };
    

    Und da findet er die Funktionen nicht, bzw er kann sie nicht richtig zuordnen je nach dem wie man das jetzt genau gemacht.



  • thorondir schrieb:

    ...
    Dann hab ich mich wohl etwas missverständlich ausgedrückt, bzw etwas falsch verstanden. Ich dachte, du meinst es ginge ohne dynamic_cast, etwa in der Form (hab den Code, den ich probiert habe, nicht mehr):

    class CallbackTop {
       public:
       virtual bool equal( CallbackTop *obj ) = 0; // Gleiche Signatur wie hier in der Kind-Klasse verwenden !!!
    };
    
    template<class T>
    class Callback : public CallbackTop {
       public:
       
       bool equal( Callback<T> *obj ) {  // HIER ist dein Fehler, du musst an dieser Stelle bei den CallbackTop *obj bleiben sonst ruft er die virtuelle Funktion 
                                         // der Basisklassen auf und nicht deiner Kindklasse
       //...
       }
    };
    

    Und da findet er die Funktionen nicht, bzw er kann sie nicht richtig zuordnen je nach dem wie man das jetzt genau gemacht.

    Hab die Kommentar direkt rein geschrieben.

    Mfg Marco


Anmelden zum Antworten