Zeiger auf Memberfunktion eines anderen Objekts einer anderen Klasse



  • Also, erstmal der Code dazu:

    #include <windows.h>
    #include <string>
    
    class A {
    public:
    	A() { this->str = "A::str"; }
    	~A() {};
    	void (*test)();
    
    	std::string str;
    };
    
    class B {
    public:
    	B() { this->str = "B::str"; }
    	~B() {}
    	void test() {
    		::MessageBox(0, this->str.c_str(), "", MB_OK);
    	}
    
    	std::string str;
    };
    
    int main() {
    
    	A a;
    	B b;
    	//a.test = b.test;  // Läuft nicht :(
    	//a.test = &b.test;  // Läuft nicht :(
    	//a.test = b.&test;  // Läuft nicht :(
    	// a.test = &(b.test);  // Läuft nicht :(
    	//a.test();
    
    	return 0;
    }
    

    Was ich jetzt möchte ist, dass ich dem Objekt von A einen Zeiger auf die Funktion "test" von einem Objekt von B zuweise. So dass ich dann also a.test() aufrufen kann, mich aber dann im Aufruf in dem Objekt von B befinde. Der this-Zeiger innerhalb der Funktion "test", die ich über a.test() aufrufen will soll also auf mein Objekt von B zeigen.

    Wenn ich die Funktion "test" in B static mache, dann kann ich zwar einen Zeiger darauf zuweisen, aber dann kann ich nicht auf die Member in B zugreifen. 😞

    Irgendwie ein wenig quer. 🙂

    Frage ist: Geht das überhaupt, und wenn ja, wie?



  • Warum hällst du dir nicht das Objekt und rufst dann einfach die Funktion des Objekts auf?

    Fänd ich wesentlich lesbarer....

    Ansonsten würde ich es mal mit void (B::*test) (); versuchen...



  • hm, vielleicht denke ich ja auch verkehrt, ich erklär mal wofür das sein soll.

    Ich versuche mich zwecks Übung und Vertiefung gerade an einem kleinen (WinAPI)-Wrapper. Wenn ich jetzt ein Fenster (Klasse Window) und einen Button (Button) habe, dann möchte ich z.B. eine Klasse MainWnd von Window als Fenster ableiten und dort eine Funktion "OnButton1Click" haben. Diese Funktion soll aufgerufen werden, wenn in der Nachrichtenverwaltung für den Button quasi "this->OnClick" aufgerufen wird. Also vom Prinzip her sowas:

    class Window {
    ...
    };
    
    class Button {
    ...
      void (*OnClick)(...);
    ...
    };
    
    class MainWnd : public Window {
    ...
      // Im Konstruktor Controls definieren, etc.
      MainWnd {
        this->Button1 = new Button(...);
        this->Button1->OnClick = this->OnButton1Click;
      }
      void OnButton1Click(...) {
        ...
        // Hier z.B. das Caption des MainWnd-Fenster ausgeben mit
        ::MessageBox(0, this->Caption, "", MB_OK);
      }
    ...
    };
    

    Ist zwar bezogen auf die WinAPI, aber doch eher ein C++-Problem, denke ich. Vielleicht auch ein Design-Problem, aber ich wüsste nicht, wie ich evtl. sonst lösen sollte. Button ist hier nur ein Beispiel, dies speziell könnte man ja noch im Parent über WM_COMMAND auswerten, aber es gibt da ja auch Nachrichten, die nicht an das Parent gesendet werden, bzw. wenn ich ein ganz normales Fenster als Child eines anderen definiere, dann könnte ich so die Nachrichten in Extra-Funktionen handeln, ohne, dass ich eine Klasse ableiten müsste, wo dann die Funktionen geerbt werden. Also, ich könnte auch z.B.

    class Button {
      ...
      void OnClick(...);
      ...
    };
    
    // und dann:
    class CloseButton {
      ...
      void OnClick(...) {
        // Hier spezieller OnClick-Code
      }
    };
    

    machen, aber dann müsste ich für jedes Control, wo ich evtl. die eine oder andere Nachrichtenbehandlung ändern möchte eine eigene Klasse ableiten, was ich nicht so schön finde.
    Also, wenn es am Design-Ansatz liegt, dann wäre ich auch für andere Vorschläge sehr dankbar. 😃



  • Hallo,
    schau dir mal boost::function und boost::signal an.



  • Werd' ich machen. Vielen Dank schonmal.



  • Hmmm das allgemeine Problem würde man wohl als Message-Handling bezeichen. 🙂
    Dafür fliegen bestimmt irgendwo (z.B. WWW) schon ein paar Entwurfsmuster rum...

    Tendenziell würde ich an dieser Stelle nicht mit Funktionspointern arbeiten, weil das ganze Objektbezogen ist, Funktionspointer extrem gut lesbar sind und man auf das betroffene Objekt für gewöhnlich sowieso zugreifen möchte....



  • OK, nach langem Suchen einiges an Leserei habe ich nun eine Lösung(?) gefunden, allerdings scheint mir die etwas "dirty" und nicht sehr typsicher zu sein, kann man den vorhandenen Code evtl. umschreiben, so dass zum einen die Zuweisung der Member-Funktion nicht so unschön erscheint und zum anderen eine gewisse Sicherheit gegeben ist?

    Hier erstmal der Code:

    #include <windows.h>
    #include <string>
    
    // onClickEvent
    template <class TClass> class TOnClickEvent {
    private:
    	void (TClass::* myFunction)();
    	TClass* myObject;
    public:
    	TOnClickEvent(TClass* obj, void (TClass::* fn)()) {
    		myObject   = obj;
    		myFunction = fn;
    	};
    	void operator()() {
    		(*myObject.*myFunction)();
    	};
    };
    
    class Widget {
    private:
    	TOnClickEvent<Widget>* OnClick;
    public:
    	void SetOnClick(TOnClickEvent<Widget>* evFn) {
    		this->OnClick = evFn;
    	}
    	void DoClick() {  // Funktion, um Click zu simulieren, wird später in der callback aufgerufen
    		if (this->OnClick != 0) {
    			(*this->OnClick)();
    		}
    	}
    };
    
    class Button : public Widget {
    public:
    };
    
    class Window : public Widget {
    public:
    	Button *b1, *b2;
    	std::string Caption;
    	Window() {
    		Caption = "Window";
    		b1 = new Button();
    		b2 = new Button();
    		// set event-handler
    		b1->SetOnClick((TOnClickEvent<Widget>*)new TOnClickEvent<Window>(this, &Window::b1Click));
    		b2->SetOnClick((TOnClickEvent<Widget>*)new TOnClickEvent<Window>(this, &Window::b2Click));
    	}
    	void b1Click() {
    		::MessageBox(0, this->Caption.c_str(), "", MB_OK);
    	}
    	void b2Click() {
    		::MessageBox(0, std::string(this->Caption + " Button 2").c_str(), "", MB_OK);
    	}
    };
    
    int main() {
    	Window* mywin = new Window();
    	mywin->Caption = "mywin";
    	mywin->b1->DoClick();
    	mywin->b2->DoClick();
    
    	return 0;
    }
    

    In der jetzigen Variante wäre eine Zuweisung der Form:

    b1->SetOnClick((TOnClickEvent<Widget>*)new TOnClickEvent<Window>(this, &Button::DoClick));
    

    laut Compiler noch möglich, aber ich bekomme natürlich eine unknown Exception, wenn ich das Programm ausführe, da ein Objekt vom Typen Window keine Methode DoClick hat.

    Vielen Dank schonmal 🙂



  • ohman ist das kompliziert.
    wieso nicht einfach das konzept von Java-Api ( die Listeners ) übernehmen?

    class ActionListener
    {
        public: 
            void onAction(Object sender) = 0;
    }
    
    class Button
    {
        public:
        Container<ActionListener*> actionListeners;
    
        private:
           void fireAction()
           {
               foreach ( ActionListener l in actionListeners )
                   l->onAction(this);
           }
    }
    
    class Window : public ActionListener
    {
        public:
            Window()
            {
                 Button* button1 = new Button();
                 button1->actionListeners.add(this);
    
                 Button* button2 = new Button();
                 button2->actionListeners.add(this);
            }
    
            void onAction(Object sender)
            {
                 if ( sender == button1 ) // ...
                 if ( sender == button2 ) // ...
            }
    }
    

    ist auf die schnelle geschrieben, und ein wenig pseudocode.
    aber wieso einfach wenn es kompliziert geht

    b1->SetOnClick((TOnClickEvent<Widget>*)new TOnClickEvent<Window>(this, &Window::b1Click));

    🤡



  • Vielen Dank für die Idee mit den Listenern, leider stört mich dabei die Unterscheidung anhand des Sender-Objekts. Ich hätte gerne eine Member-Funktion pro Ereignis pro Button. Ich habe jetzt noch etwas getüftelt und bin auf folgenden Code gekommen.

    Evtl. könnte mir jmd., der etwas (oder gerne auch sehr viel :)) mehr Ahnung davon hat, als ich mir sagen, ob das so in Ordnung ist. Zumindest die Schnittstelle erscheint mir so typsicher.

    #include <windows.h>
    #include <vector>
    #include <string>
    
    template <class T> class TOnClick {
    	T* o;
    	void (T::* f)();
    public:
    	TOnClick(T* _o, void (T::* _f)()) {
    		o = _o;
    		f = _f;
    	}
    	void operator()() {
    		(*o.*f)();
    	}
    };
    
    class Widget {
    public:
    	std::vector<TOnClick<Widget>*> onClickListener;
    	template <typename T> void AddOnClickListener(T* o, void (T::* f)()) {
    		onClickListener.push_back((TOnClick<Widget>*)new TOnClick<T>(o, f));
    	}
    	void onClick() {
    		for (unsigned int i = 0; i < onClickListener.size(); i++) {
    			(*onClickListener[i])();
    		}
    	}
    };
    
    class Button : public Widget {
    public:
    	std::string c;
    	Button(std::string _c) {
    		c = _c;
    	}
    	void test() {
    		::MessageBox(0, c.c_str(), "", MB_OK);
    	}
    };
    
    class tmp {
    public:
    	std::string c;
    	tmp(std::string _c) {
    		c = _c;
    	}
    	void tmp2() {
    		::MessageBox(0, std::string("tmp::tmp2 | " + c).c_str(), "", MB_OK);
    	}
    };
    
    class Window : public Widget {
    public:
    	std::string c;
    	Button* b1;
    	tmp* t1;
    	Window() {
    		c = "Window";
    		t1 = new tmp("");
    		b1 = new Button("Window::b1 | Button 01");
    		b1->AddOnClickListener(this, &Window::b1Click);
    		b1->AddOnClickListener(b1, &Button::test);
    		b1->AddOnClickListener(t1, &tmp::tmp2);
    	}
    	void b1Click() {
    		::MessageBox(0, this->c.c_str(), "", MB_OK);
    	}
    	void b1Click2() {
    		::MessageBox(0, "b1Click2", "", MB_OK);
    	}
    };
    
    int main() {
    	Window* mywin = new Window();
    	Window* mywin2 = new Window();
    	mywin->c = "mywin";
    	mywin->b1->c = "mywin 01 | Button 01";
    	mywin->t1->c = "t1";
    
    	mywin2->c = "mywin 02";
    	mywin2->b1->c = "mywin 02 | Button 01";
    	mywin2->t1->c = "t2";
    
    	mywin->b1->onClick();
    	mywin2->b1->onClick();
    
    	return 0;
    }
    

    Vielen Dank !


Anmelden zum Antworten