Pointers to C++ Member Functions



  • Hallo zusammen,

    ich hab mir gerade mal die "Pointers to C++ Member Functions"
    angekuckt. Worin liegt der Vorteil gegenüber pure virtual classes
    mit einer Methode? Ich kann mir nur vorstellen, dass man
    eine Klasse, die auf zwei verschiedene Listener hört dann beispielsweise
    beide auf die gleiche Funktionen (Voraussetzung gleiche Signatur) verweisen kann,
    wobei man sonst zwei Funktionen gebraucht werden.

    Oder wofür brauch man sie sonst?
    Irgendwie versteh ich das noch nicht 😞

    Gibt es eine Möglichkeit einen "Pointer to C++ Member Functions"
    ohne Kenntnis einer Basisklasse zu Verwenden?

    Gruß,
    *Cpp-Anfänger*



  • ich hab sowas mal gebraucht, glaube ich. dateidatum 15.7.97, kann also auch sein, daß es inzwischen bessere lösungen gibt. und vor allem war ich damals nube. keine ahnung von allem. habs nur zusammengefrickelt. hmm. heute hab ich eigentlich auch das gefühl, keine ahnung zu haben und frickle meine lösungen zusammen. bin aber kein nube mehr. muss das mal untersuchen. gibt es einen, der das gefühl hat, c++ "ganz verstanden" zu haben? also mit basic, pascal und perl(außer rekursiven regexps) und php ist "ganz verstanden" was normales. mist, deine programmierfrage stürzt mich in erhebliche philosophische probleme. erstmal hier dern angekündigte code:

    class MessageBase
    {//Abstrakte Basisklasse für alle Messages. Zeiger 
    	//hierauf werden im Simulator verwaltet.
    private:
    	Time timeToWakeUp;
    public:
    	MessageBase(Time ttwu)
    		:timeToWakeUp(ttwu)
    	{
    	};
    	MessageBase(const MessageBase &m);//forbidden
    	MessageBase &operator=(const MessageBase &m);//forbidden
    	virtual ~MessageBase(void)
    	{
    //		cout<<"test\n";
    	};
    	Time getTime(void)
    	{
    		return timeToWakeUp;
    	};
    	virtual void shoot(void)=0;
    	virtual void deactivate(void)=0;
    	virtual SimObject *getTarget(void)=0;
    	bool operator<(const MessageBase &m)
    	{
    		return timeToWakeUp<m.timeToWakeUp;
    	};
    };
    
    template<class TARGET1,class TARGET2,class O1,class O2>
    class Message2:public MessageBase
    {//Nachricht mit zwei Parametern
    private:
    	TARGET1 *target;
    	void(TARGET2::*function)(O1,O2);
    	O1 object1;
    	O2 object2;
    public:
    	Message2(TARGET1 *t,Time ttwu,void(TARGET2::*f)(O1,O2),O1 o1,O2 o2)
    		:target(t),
    		MessageBase(ttwu),
    		function(f),
    		object1(o1),
    		object2(o2)
    	{
    		target->_incCountOfMessages();
    	};
    	~Message2(void)
    	{
    		deactivate();
    	};
    	void shoot(void)
    	{
    		if(target!=NULL)
    			(target->*function)(object1,object2);
    	};
    	void deactivate(void)
    	{
    		if(target!=NULL)
    		{
    			target->_decCountOfMessages();
    			target=NULL;
    		};
    	};
    	SimObject *getTarget(void)
    	{
    		return target;
    	};
    };
    

    das war glaube ich ein wesentlicher teil eines universalsimulators, wo ich davon ausging, daß methodenaufrufe genau das gleich sind wie gesendete nachrichten und ich dazugefummelt habe, daß man stets innerhalb des simulators nachrichten auch zeitverzögert schicken kann. der universalsimulator sollte aber keine kenntnis haben müssen, *welche* nachrichten geschickt werden. sonst hätte ich den ja die ganze zeit aktualisieren müssen, wenn in der simulierten welt wieder eine neue methode eingeführt wird.

    das andere aufgeworfene problem harrt der lösung. falls ich es löse, poste ich. ich fürchte aber, es ist eher hirnverquirlend und ich werde mich zurückziehen müssen.



  • Danke volkard!

    Dass nenn ich mal krassen C++-Code



  • Da gibts einige Dinge die mit einem Pointer auf eine Klasse (mit oder ohne virtuelle Funktionen) nicht gehen. Grundsätzlich kann man sagen: um eine member function aufzurufen brauchst du (abgesehen von den zu übergebenden Parametern) zwei Dinge: den "this" Zeiger und den member function pointer. Der "this" Zeiger bestimmt das Objekt, der member function pointer die Funktion.

    Wenn du also z.B. irgendwo eine ganze Liste von Objekten gleichen Typs hast, und du willst auf alle Objekte eine bestimmte Funktion aufrufen, aber erst zur Laufzeit festlegen welche Funktion, dann brauchst du keinen "this" Zeiger, sondern einen member function pointer.



  • Es gibt hierfür nicht wirklich viele Verwendungszwecke.

    Für die objektorientierte Programmierung braucht mans nicht, da kann man auch gleich mit einem Interface (Objekten) arbeiten und bei der funktionalen Programmierung gibt es keine Objekte/Memberfunktionen.

    Der einzige Verwendungszweck, der mir so einfällt ist die Definition von Schnittstellen um C++-Code von C aus nutzen zu können. Dann kann man entweder mit Funktionspointern arbeiten oder eben mit einer Memberfunktion eines Objektes.

    Ich persönlich kann mich mit Pointer auf Memberfunktionen nicht so richtig anfreunden. Ich halte nicht so viel davon auf diese Weise funktionalen Code mit objektorientierten Code zu mischen.



  • Wenn du also z.B. irgendwo eine ganze Liste von Objekten gleichen Typs hast, und du willst auf alle Objekte eine bestimmte Funktion aufrufen, aber erst zur Laufzeit festlegen welche Funktion, dann brauchst du keinen "this" Zeiger, sondern einen member function pointer.

    Diese Problemstellung läßt sich durchaus auch anders lösen.
    Das Auswerten einer Variable (Enum/Int) innerhalb der Abarbeitungs-Schleife macht genau das selbe, wenn es auch nicht so performant (das Verhalten bleibt jedoch linear). Dafür ist es allerdings wesentlich sicherer und der Code ist verständlich. 😉



  • Mathias schrieb:

    Das Auswerten einer Variable (Enum/Int) innerhalb der Abarbeitungs-Schleife macht genau das selbe, wenn es auch nicht so performant (das Verhalten bleibt jedoch linear).

    switch ist performanter als der virtuelle funktionsaufruf!
    💡
    da geht nämlich inlining bis unten hin und es gibt keinen optimierungshorizont.



  • Mathias schrieb:

    Für die objektorientierte Programmierung braucht mans nicht, da kann man auch gleich mit einem Interface (Objekten) arbeiten

    man *braucht* gar nix, denn man kann wie in basic v2 arbeiten.

    und bei der funktionalen Programmierung gibt es keine Objekte/Memberfunktionen.

    aber funktionen sind objekte. damit geht so einiges.

    Der einzige Verwendungszweck, der mir so einfällt ist die Definition von Schnittstellen um C++-Code von C aus nutzen zu können. Dann kann man entweder mit Funktionspointern arbeiten oder eben mit einer Memberfunktion eines Objektes.

    fällt die noch was praktisches ein, wenn du meinen code oben anguckst?

    Ich persönlich kann mich mit Pointer auf Memberfunktionen nicht so richtig anfreunden. Ich halte nicht so viel davon auf diese Weise funktionalen Code mit objektorientierten Code zu mischen.

    estwas ist doch nicht oo, weil man immer "klassen" verwendet. kommt noch so weit, daß du verwendung von std::string oo nennst und verwendung von char* funtional nennst.



  • Mathias schrieb:

    ...
    Diese Problemstellung läßt sich durchaus auch anders lösen.
    ...Dafür ist es allerdings wesentlich sicherer und der Code ist verständlich. 😉

    Hi,

    A) Man kann immer alles "auch anders lösen" - das ist kein Kriterium.
    😎 "Sicherheit" und "Verständlichkeit" haben oftmals etwas damit zu tun, wie gut man sich mit den verwendeten Konstrukten auskennt. Natürlich versteht man die intuitiver, die man selbst permanent verwendet und macht mit den anderen öfter Fehler. Aber geade deswegen finde ich es wichtig, sich immer wieder neu mit den Konstrukten einer Sprache auseinanderzusetzen, die man noch nicht so kennt.

    Leider habe ich festgestellt, dass es bei Programmierern häufig die Tendenz gibt, sich immer mehr auf die paar Techniken zurückzuziehen, die "man immer schon so gemacht" hat. ... gleichzeitig sind das oft die Leute, die dann über "unverständlichen Code" klagen. (ich will natürlich nicht behaupten, dass das bei Dir persönlich der Fall ist - ich kenne Dich nicht genug).
    Das kann ich dann aber nicht ganz ernst nehmen. Wenn man sich einmal ein wenig mit "member function pointers" vertraut gemacht hat, ist der Code auf jeden Fall lesbarer (weil kürzer, prägnanter, einfacer erweiterbar und aussagekräftiger) als bei einem "Monster-switch".

    Gruß,

    Simon2.



  • volkard schrieb:

    Mathias schrieb:

    Das Auswerten einer Variable (Enum/Int) innerhalb der Abarbeitungs-Schleife macht genau das selbe, wenn es auch nicht so performant (das Verhalten bleibt jedoch linear).

    switch ist performanter als der virtuelle funktionsaufruf!

    Ich behaupte das Gegenteil. Zumindest wenn der eigentliche Code in einer Funktion steckt, die in der case-Anweisung aufgerufen wird und wir gegen einen "normalen" virtuellen Aufruf vergleichen (also nicht gegen einen Aufruf über einen Memfun-Pointer).

    Ich habe zumindest bisher an verschiedenen Stellen gelesen, dass auf modernen Prozessoren (mit ihren langen Pipelines) ein switch-Statement nicht nur langsamer als eine if-elseif-else-Kette ist, sondern auch im Gegensatz zu einem virtuellen Aufruf schlechter abschneidet, was wohl an der schlechteren Branch-Prediction liegen soll.

    Auf meinem VC8 schneidet ein switch sogar schlechter als ein indirekter Aufruf über einen MemFun-Pointer ab.



  • ich gehe natürlich von kleinen funktionen aus, die normalerweise inline sind. diese ganzen trivialen weiterreicher und nichtsmacher, die bei mir meistens hinter so einem switch liegen würden. und da kann der optimierer manchmal schhon einiges (insbesondere bei nichtsmachern).

    Ich habe zumindest bisher an verschiedenen Stellen gelesen, dass auf modernen Prozessoren (mit ihren langen Pipelines) ein switch-Statement nicht nur langsamer als eine if-elseif-else-Kette ist, sondern auch im Gegensatz zu einem virtuellen Aufruf schlechter abschneidet, was wohl an der schlechteren Branch-Prediction liegen soll.

    ok, bevor ich wiedermal switch bevorzuge (was alle 5 jahre mal vorkommt), messe ich, ob ein verzweigungsbaum oder der virt aufruf schneller ist.



  • volkard schrieb:

    Ich habe zumindest bisher an verschiedenen Stellen gelesen, dass auf modernen Prozessoren (mit ihren langen Pipelines) ein switch-Statement nicht nur langsamer als eine if-elseif-else-Kette ist, sondern auch im Gegensatz zu einem virtuellen Aufruf schlechter abschneidet, was wohl an der schlechteren Branch-Prediction liegen soll.

    ok, bevor ich wiedermal switch bevorzuge (was alle 5 jahre mal vorkommt), messe ich, ob ein verzweigungsbaum oder der virt aufruf schneller ist.

    Das ist sicher das Beste, denn wie gesagt: ich habe das auch nur gelesen. Da es mir während der Lektüre logisch erschien und ich wahrlich kein Fan von switch-Anweisungen bin, habe ich auf eine empirische Prüfung verzichtet 😉

    Die Geschichte mit dem if-elseif-else vs. switch kann ich allerdings zumindest für den gcc in Version 3.x bestätigen. In einem ASP-Solver-Projekt hat sich die Umwandlung kurzer switch-Anweisungen in if-elseif-else-Zweige in performance-kritischen Funktionen durchaus gelohnt.



  • Oh Mann hier wird schon wieder was gelabert. Funktional, hm, wissen wir denn auch was das heisst? Member function pointer von C aus aufrufen, hm, ja? Und klar ist ein indirekter call oft schneller als ein if/switch, bissi über CPU Architektur sollte man schon wissen. Im übrigen an alle "geht auch anders" schlaumeier: man braucht sowieso für überhaupt nix irgendwelche Pointer, das lässt sich alles mit Indices und switches und überhaupt realisieren, Schleifen tun wir auch weg weil wir goto haben, virtuelle Funktionen machen wir von nun an auch über switch -- am besten tun wir gleich das ganze C++ und C weg und programmieren wieder alles in FORTRAN. Juche!



  • @volkard
    Funktionen sind Objekte?

    Bin ich unter die Mathematiker geraten?

    Ich definiere meine Basisdatentypen und Funktionen als Objekte und programmiere von nun an objektorientiert in C. 😃

    estwas ist doch nicht oo, weil man immer "klassen" verwendet. kommt noch so weit, daß du verwendung von std::string oo nennst und verwendung von char* funtional nennst.

    Naja irgendwo muß man schonmal den Schlußstrich ziehen aber C-Arrays entspringen ganz sicher nicht dem objektorientierten Ansatz. 🙂

    @Simon2
    Es ist richtig, dass es kein Argument ist das man etwas anders lösen kann.
    Ich finde dieses Sprachmittel nur sehr bescheiden, weil man es mit einfacheren Sprachmitteln meiner Meinung nach besser (ich schreibe C++ nicht für mich, das müssen andere nachher auch verstehen) lösen kann.
    Wenn alle meine Kollegen bis ins letzte alle Besonderheiten von C++ benutzen und dort wo es Schick ist anfangen die Zeigerarithmetik bis ins letzte Auszureitzen, verbringe ich und meine Kollegen sehr viel Zeit damit zu verstehen was wo wie passiert. Dies ist unabhängig davon wie gut wir sind.

    Abgesehen davon find ich dieses Vorgehen wirklich unsicher. Ich hantiere nur sehr ungern mit Speicher-Adressen und Offsets auf der gleichen herum, weil es einfach fehleranfällig ist (gerade wenn n Blackboxen hat die miteinander komunizieren).
    Nicht umsonst hat man es z.B. in Java soweit wie möglich vermieden.



  • Mathias schrieb:

    @volkard
    Funktionen sind Objekte?

    In funktionalen Programmiersprachen schon.

    Ich definiere meine Basisdatentypen und Funktionen als Objekte und programmiere von nun an objektorientiert in C. 😃

    Das geht relativ easy: GTK+ beweist das.



  • @Mathias/all:

    Ein Einsatzzweck sind z.B. signal/slot Implementierungen. Das lässt sich in C++ IMHO immer noch am schönsten, einfachsten und verständlichsten über mem-fun-ptr lösen. Klar kann man da auch mit Hilfe von templates automatisiert call-stubs erzeugen lassen und dann ganz normale function pointer verwenden oder sogar caller Objekte und virtual functions, aber das ist IMHO noch unübersichtlicher, wahrscheinlich langsamer, und deutlich mehr Code für nix.

    Vonwegne Sicherheit und Verständlichkeit und Code den andere auch lesen können... also dann müsste ich auch aufhören Templates zu verwenden, oder State-Machines oder diverse Pattern ala Visotor etc.
    Das verstehen auch viele Leute nicht. Andrerseits gibt es viele Dinge die mit Hilfe von Templates/State-Machines/... am elegantesten und einfachsten zu lösen sind wenn man erst einmal diese "Werkzeuge" verstanden hat. Nur weil das vielleicht nicht jeder tut werde ich nicht Code schreiben der für mich komplizierter ist als Code der mit einfacheren Mitteln aufgebaut ist, dafür aber schwer zu warten UND schwer als ganzes zu verstehen ist.



  • Mathias schrieb:

    @volkard
    Funktionen sind Objekte?

    äh, jo. es sind dinge, die mit einem namen ansprechbar sind und die operatoren haben (mindestens & und ()). sie haben einen typ. wo ist das problem. wenn int i=5 ein objekt ist, warum nicht auch int f(){return 5;}?

    Bin ich unter die Mathematiker geraten?

    ich bin eigentlich kein mathematiker. aber wenn sie ganz ganz langsam sprechen und gelegentlich bildchen malen, kann ich sie oft verstehen.

    Ich definiere meine Basisdatentypen und Funktionen als Objekte und programmiere von nun an objektorientiert in C. 😃

    jo, klar. wie willste sonst in c was größeres programmieren?

    Naja irgendwo muß man schonmal den Schlußstrich ziehen aber C-Arrays entspringen ganz sicher nicht dem objektorientierten Ansatz. 🙂

    sie entspringen ihm nicht. so wie integers auch nicht. aber sie harmonieren damit durchaus.

    man kann in jeder sprache c++-programme schreiben. nur isses in c++ leichter.


Anmelden zum Antworten