Klassenfrage :)



  • Hallo und guten Tag,
    Ich programmiere ein in c von mir selbst geschriebenes Programm in c++ um, weil es mir in c zu komplex ist und ich auf die Objektorientiertheit von c++ setzte. Nun meine Frage:
    Ist es möglich einem Objekt einen Funktionszeiger auf eine Funktion des Hauptprogramms (in dem die main Funktion definiert ist) zu übergeben, so das ich in diesem Objekt Methoden anderer Klassen aufrufen kann; oder muss ich mein Objektdesing ändern?

    Z.B.: Ein Objekt der Klasse Wurm ist einem Objekt der Klasse Apfel übergeben worden und soll Methoden der Klasse Apfel aufrufen. Abre der Apfel soll hauptsächlich Methoden des Wurmes Aufrufen


  • Mod

    Ja, klar geht das. Was aber wichtig ist und worüber du dir offensichtlich noch keine Gedanken gemacht hast: Auf welchem Objekt soll der Aufruf erfolgen? Für ein bestimmtes Objekt, das der Nutzer bei der Übergabe entscheidet? Für ein Objekt das der Aufrufer bestimmt und der Nutzer hat nur gesagt, welche Funktion?

    #include <iostream>
    #include <functional>
    
    class Foo
    {
    	int i;
    public:
    	Foo(int i) : i(i) {}
    	void func1() { std::cout << "Func 1 von Foo(i=" << i << ")\n"; }
    	void func2() { std::cout << "Func 2 von Foo(i=" << i << ")\n"; }
    };
    
    class Caller
    {
    	void (Foo::* unbound_foo_member)();
    	std::function<void()> bound_member;
    public:
    	Caller(
    		void (Foo::*unbound_foo_member)(void), 
    		std::function<void()> bound_member) :
    		unbound_foo_member(unbound_foo_member), 
    		bound_member(bound_member)  {}
    
    	void call()
    	{
    		Foo f(123);
    		(f.*unbound_foo_member)();
    		bound_member();
    	}
    };
    
    int main()
    {
    	Foo f(456);
    	std::function<void()> bound_func1 = [&f] {f.func1(); };
    	Caller c(&Foo::func2, bound_func1);
    	c.call();	
    }
    

    @EL-europ sagte in Klassenfrage 🙂:

    Z.B.: Ein Objekt der Klasse Wurm ist einem Objekt der Klasse Apfel übergeben worden und soll Methoden der Klasse Apfel aufrufen. Abre der Apfel soll hauptsächlich Methoden des Wurmes Aufrufen

    Klingt aber trotzdem als ob da ein Fehler im Design ist, wenn Objekte hauptsächlich die Methoden anderer Klassen aufrufen und umgekehrt. Es ist äußerst ungewöhnlich, dass man das überhaupt hat, noch viel mehr wenn das etwas ist, was häufig bei dir vorkommt. Du merkst ja sicher auch, das der Code dort oben ziemlich unschön ist und nicht so aussieht, wie übliche C++ Programme (zumindest nicht wie übliches C++ im Jahr 2024, vor 30 Jahren kann das teils so ausgesehen haben).



  • @EL-europ sagte in Klassenfrage 🙂:

    Ich programmiere ein in c von mir selbst geschriebenes Programm in c++ um, weil es mir in c zu komplex ist und ich auf die Objektorientiertheit von c++ setzte.

    Oh schade, und was machst du nun aktuell? Oder war die Vergangenheitsform nur ein Versehen/Rechtschreibfehler?



  • @omggg
    Ich hab nee Idee für ein Programm und wollte c lernen, nur werden mir jetzt die c- Dateien zu Umfangreich. Und ich denke mit einem objektorientierten Ansatz besser zu fahren. Die Antwort von SeppJ bin ich grad am versuchen zu verstehen und zu übertragen. Wird wohl morgen werden bis ich selbst neue Erkenntnis erlangt habe 😉



  • oder ... verabschiede dich vom alten C++ und nimm was Neueres, was nicht obsolet ist. 😉 Das Entwicklungsrad steht ja nicht still ... andere OO-Programmiersprachen haben weniger Boilerplate/Overhead und dafür mehr "Zucker". 🙂 Das soll dann auch helfen, Spaghetticode zu vermeiden.



  • @omggg
    Ich hab das Prog vor Corona in python geschrieben, aber die Grafikausgabe war zu langsam (Darstellung der Änderung der Mandelbrondtmenge über die "Iterationstiefe") und die "quick n dirty" lösungen wurde immer mehr. Dann hab ich mit diesen RPI-Pico boards und dessen c-sdk gespielt.Vor ner woche hab ich das Mandelbrodt zur hälfte in c geschrieben und die Dateien sind mir zu lang (Aber die Grafik über Framebuffer ist schnell genug). Natürlich ist es so das ich c auch nur unzureichend beherrsche und es vielleicht auch mit python schneller ginge, aber das Leben ist ein Geschenk 😃
    Entwickeln tue ich auf einem Raspi 5 mit Geany/Kommandozeile.
    Ich mach mir erst mal "tiefgehendere" Gedanken über ein Klassendesing von Mandelbrodt (Die GUI Ist selbstgemacht), Und werde die entsprechenden Kapitel in Jürgen Wolf's c++11 lesen. Oder gibt es eine Onlninefom die vielleicht sogar besser ist.
    Herr Wolf ist in diesem Buch immer sehr theoretisch mit seinen Beispielen.



  • @omggg
    Um etwas genauer zu sein:
    Es gibt die Klasse Display mit Methoden um in den FB zu Zeichnen und zu Schreiben. Ausschließlich ein Objekt dieser Klasse wird einem Objkt klasse ui_interface übergeben das sich selbst darüber darstellt. Das Objekt der Klasse ui_interface wird einem Objekt der Klasse Apple Übergeben das die berechnung der Mandelbrodtmenge durchführt und diese über die Methode "ui_interface->diplay->putPixel" darstellt. Die Parameter der Berechnung ist das Objekt ui_interface. Und auch die Buttons sind in ui_iterface. Dort genau komme ich nicht weiter, eine Apple->Methode aufrufen wenn in ui-interface::onMouseOver x und y passen. (Die Mausfunktion ist nicht das Problem sondern der Methodenaufruf)



  • @SeppJ
    Danke für die Antwort, aber ich steige leider nicht dahinter was die Definition von Caller bedeuted. Wie du selbst schon andeutest ist das ziemlich umständlich.


  • Mod

    Ist nur ein Beispiel für die beiden Modi, die ich erklärt habe:

    • unbound_foo_member ist ein Zeiger auf eine Methode der anderen Klasse Foo und der Nutzer dieses Zeigers muss selber irgendwo ein Foo-Objekt herbekommen (hier erstellt der Caller dafür das Objekt mit i=123)
    • bound_member ist ein allgemeiner Zeiger auf eine beliebige Funktion (mit der passenden Signatur), und man hat ihm hier ein funktionsartiges Objekt zugewiesen, das den Aufruf an das Foo-Objekt (i=456) aus der main koppelt. Man kann dem gar nicht mehr ansehen, dass das irgendwie mit Foo zu tun hat, es ist nicht unterscheidbar von einem normalen Funktionszeiger (außer dass der Typ dahinter irgendetwas wildes ist)

    Dass ich das in die Klasse Caller verpackt habe, kommt wegen deiner Fragestellung, dass du das unbedingt einer Klasseninstanz übergeben wolltest. Das Prinzip ist aber völlig unabhängig davon, wie man es benutzt. void (Foo::*)(void) mag zwar kompliziert aussehen, aber ist auch nur ein Typ wie jeder andere Variablentyp und nutzt sich genauso wie int.

    Ich möchte aber nochmals betonen, dass das äußerst ungewöhnlich ist, so viel (oder überhaupt irgendetwas) in C++ mit Memberfunktionszeigern machen zu wollen, und du dein Design nochmal überdenken solltest. In C kann ich das ja noch sehen (und da sind es ja auch keine richtigen Memberfunktionen), um so etwas wie virtuelle Funktionen zu emulieren. Ist es vielleicht das, was du wirklich willst? Virtuelle Funktionen? Und hast noch nicht davon gehört weil du neu in C++ bist?

    omggg ist unser örtlicher Dauertroll. Ich würde an deiner Stelle dessen Beiträge einfach komplett ignorieren.



  • @SeppJ
    Haha, immerhin half mir der Troll meine Frage etwas zu konkretisieren.
    Ich meine: Ist es möglich Apple->schrumpf() aus Apple->Wurm->friss() aufzurufen? und, würdest du bitte eine der beiden Möglichkeiten nochmals isoliert für mich dartellen, wenn nicht gar die Antwort auf die Frage?
    Ich verstehe vor allem die Definition Von Caller(...) unter public nicht



  • @EL-europ
    Ich bin kein Programm, aber @SeppJ mag mich nicht, deshalb schreibt er oder sie das.

    Auf deine Fragen kann ich zurzeit aber nicht näher eingehen.



  • @omggg
    ok, danke



  • @SeppJ Ichversuch mal deinen Code zu beschreiben
    in der Klasse Caller
    wird mit "void (foo.." ein Zeigerobjekt auf irgendeine Methode der Klasse Foo angelegt,
    und mit "std::func.."eine std::function mit dem namen "bound_member" und dem Rückgabewert void deklariert.
    Dann unter public folgt der Konstruktor von Caller dem zwei Parameter und dazu zwei ppassende Standartwerte übregeben werden? Und in der Methode call() wird "einfach" auf den Konstruktor von Foo zugegriffen um dann über das Objekt f.*unbound_foo_member auf eine in den Konstrutionsparametern übergebene Funktion in Foo zugreifen zu können und dann wird die std::function bound_member aufgerufen.

    Wenn das stimmt ist das mir zu Abtrakt um es auf Apfel und Wurm übertragen zu können.
    Kannst du bitte den Konstruktor von Caller( wenn es den überhaupt einer ist) und die "Schritte" in main näher erläutern


  • Mod

    Da, ganz ohne drumherum.

    #include <iostream>
    #include <functional>
    
    
    using namespace std;
    
    
    class Foo
    {
    	int i;
    public:
    	Foo(int i) : i(i) {}
    	void func1() { std::cout << "Func 1 von Foo(i=" << i << ")\n"; }
    };
    
    
    int main()
    {
    	Foo f(456);
    	std::function<void()> bound_func1 = [&f] {f.func1(); };
    	void (Foo::* unbound_func1)() = &Foo::func1;
    	
    	bound_func1();
    	(f.*unbound_func1)();
    }
    

    Den Caller hatte ich doch nur gezeigt, weil du dich so daran aufgehangen hattest, dass das unbedingt in einer Klasse sein müsse, als ob da dran irgendetwas besonderes wäre.



  • @SeppJ
    Ich hab eine einfache Frage ja auch kompliziert gestellt, und versuch jetzt erstmal die Übertragung deines Codes


  • Mod

    @EL-europ sagte in Klassenfrage 🙂:

    @SeppJ
    Ich hab eine einfache Frage ja auch kompliziert gestellt, und versuch jetzt erstmal die Übertragung deines Codes

    Nein! Werd' lieber dein Design los, das dich denken lässt, dass dies überhaupt nötig wäre! Du übersiehst garantiert irgendwas simples, das schon in der Sprache eingebaut ist. Dazu musst du natürlich erst einmal die Sprache besser lernen. Aber das wäre was gutes, denn man sollte schließlich die Sprache beherrschen, die man spricht.



  • @SeppJ
    Mir fällt nix besseres ein. Ich müsste ja eigentlich eine "abstrakte" Klasse definieren von der ich Apfel und Wurm gemeinsam ableite; Wenn ich den oop-ansatz richtig verstehe?


  • Mod

    @EL-europ sagte in Klassenfrage 🙂:

    @SeppJ
    Mir fällt nix besseres ein. Ich müsste ja eigentlich eine "abstrakte" Klasse definieren von der ich Apfel und Wurm gemeinsam ableite; Wenn ich den oop-ansatz richtig verstehe?

    Da wird ja gerade der Designfehler sein, denn das klingt offensichtlich falsch. Aber mit dem Apfel-Wurm-Beispiels kommt man da nicht weiter, weil da in der echten Welt keine tiefergehenden Beziehungen existieren, als das beides physikalische Objekte sind, und das ein Apfel einen oder mehr Würmer haben kann. In der echten Welt wird der Apfel nie etwas mit einem Wurm machen, und die Würmer werden eventuell etwas aus dem umgebenden Apfel fressen. Nichts davon klingt auch nur entfernt nach Übergabe von Memberfunktionszeigern..



  • @SeppJ Nun ja aber:
    Die Beziehung "Apfel->schrumpf() wenn Apfel->Wurm->friss()" ist gegeben.
    Aber du hast Recht, ich sollte zumindest die entsprechenden Kapitel noch mal lesen. Bis dahin 🙂


  • Mod

    Ist ein bisschen komisch, dass der Apfel sich von dem Wurm fressen lässt, aber naja, nehmen wir das mal als gegeben an, weil die Analogie vielleicht nicht perfekt ist. Aber wieso muss in dem Fall an Wurm::friss die Methode Apfel::schrumpf übergeben werden? Sollte nicht die Fressmethode wissen, dass sie den Apfel schrumpfen soll?

    class Wurm
    {
        void friss(Apfel &zu_fressender_apfel)
        {
            ...
            zu_fressender_apfel.schrumpf();
            ...
        }
    };
    

    Oder ist die Sache, dass der Wurm auch in anderem Obst sein kann? Dann wäre das, wie spekuliert, ein Fall für virtuelle Methoden:

    class Obst
    {
        virtual void schrumpf();
    };
    
    class Apfel: public Obst
    {
        virtual void schrumpf() {wie ein Apfel schrumpft;}
    };
    
    class Birne: public Obst
    {
        virtual void schrumpf() {wie eine Birne schrumpft;}
    };
    
    class Wurm
    {
        void friss(Obst &zu_fressendes_obst)
        {
            ...
            zu_fressendes_obst.schrumpf();
            ...
        }
    };
    

    Da wird dann in friss automatisch an die passende schrumpf-Methode verwiesen, je nachdem, welches Obst der Wurm frisst.

    Im Hintergrund arbeiten dann zugegebenermaßen Memberfunktionszeiger. Aber das macht halt die Sprache für dich als Feature, und du musst dich nicht mit solch fehleranfälligem Zeug mit komischer Syntax herumschlagen. In C muss man das halt selber machen, wenn man diesen Effekt will, daher hatte ich die Vermutung, dass das hier die Richtung ist, aus der du kommst.