www.mikrocontroller.net

Forum: PC-Programmierung Funktionspointer für C++ Memberfunktion


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Peter Kröner (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Moin!

Ich hab da mal ne Frage zu Funktionspointern in C++. Und zwar speziell 
zu Pointern auf Memberfunktionen einer Klasse.

Konkret brauche ich eine Timerklasse, welche eine Callbackfunction 
enthält. Das Aufrufen der Callbackfunktion für globale oder statische 
Funktionen ist ja kein Problem. Aber die mach ich das, wenn ich eine 
Memberfunktion aufrufen will? Die Memberfunktion bekommt ja unter der 
Haube bei einem Aufruf einen This-Pointer mit übergeben.

Ok, ich muss also in meiner Timerklasse den Pointer auf das Objekt und 
den Pointer auf die Funktion speichern. Ich bin mir aber noch nicht 
sicher, wie der Funktionszeiger und der Funktionsaufruf im Detail 
aussehen muss.  Die Callbackfunktion soll keine weiteren Parameter 
haben:

void BeliebigeKlasse::aufzurufendeFunktion()
{
   tuWasSchönes();
}

In der Timer Deklaration:
void* object;
void (*callback)(void*);    // Kann man den so deklarieren? der 
                            // this-Pointer taucht ja eigentlich 
                            // in der Funktionssignatur gar nicht auf?
Callback Aufruf:
callback(object);  // wird nicht funktionieren, da ich ja das, was der
                   // This-Pointer sein soll, nicht explizit der Funktion 
                   // übergeben kann


Andere Möglichkeit:
void* object;
void (*callback)();

...

object->callback();  //wird wohl auch nicht funktionieren, da man einen void-Zeiger nicht dereferenzieren kann. 

Ziel der Sache ist, in die Timerklasse keine Abhängigkeiten von der 
Klasse, aus der die Memberfunktion stammt, reinzubringen. Der Timer soll 
mit jeder beliebigen Memberfunktion (mit passender Signatur) jeder 
beliebigen Klasse funktionieren.

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das geht nicht, da zum Aufruf einer nichtstatischen Memberfunktion einer 
Klasse der this-Zeiger bekannt sein muss.

Den kannst Du natürlich als opaken void-Pointer an eine statische 
Memberfunktion übergeben, die wiederum mit einem typecast daraus einen 
korrekten this-Zeiger erzeugen und dann eine beliebige nichtstatische 
Funktion aufrufen kann.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Kröner schrieb:
> Moin!
>
> Ich hab da mal ne Frage zu Funktionspointern in C++. Und zwar speziell
> zu Pointern auf Memberfunktionen einer Klasse.

Au weh.
Einer der wunden Punkte in C++

>
> void* object;
> void (*callback)(void*);    // Kann man den so deklarieren? der
>                             // this-Pointer taucht ja eigentlich
>                             // in der Funktionssignatur gar nicht auf?
> 

Der Klassenname der aufzurufenden Klasse ist Teil der Funktionssignatur.

typedef void (* MyClass::funcPtr)( void );

Aber so richtig befriedigend ist das nicht. Besser ist diese 
Vorgehensweise:
Man schafft sich eine Interface-Callback Klasse.
class TimerCallback
{
  public:
    virtual void DoWork() = 0;
};

Im Timerobjekt wird dann ein Pointer auf das tatsächliche Objekt 
gehalten, welches dieses Interface implementieren muss. Damit kennt die 
Timerklasse das Objekt, welches benachrichtigt werden soll (wenn auch 
nur in Form eines Interfaces) und die Klasse, welche sich beim Timer um 
einen Rückruf bewirbt muss lediglich von dieser TimerCallback Klasse 
abgeleitet werden und die vorgeschriebene DoWork Funktion 
implementieren.
class Timer
{
  public:
    Timer() : callBack ( NULL ) {}

    void RegisterCallback( TimerCallback* object );
    void PerformCallback()
    {
      if( callBack )
        callBack->DoWork();
    }

  protected:
    TimerCallback* callBack;
};

class MyClass : public MyBaseClass,
                public TimerCallback   // <- das verleiht MyClass die
                                       // Fähigkeit als Callbackobjekt
                                       // fuer Timer zu fungieren
{
  public:
    
    void Something( Timer& timer )
    {
      timer.RegisterCallback( this );
    }

    virtual void DoWork()   // der Compiler wacht darüber, dass diese Funktion
    {                       // auch tatsächlich implementiert wird
    }
};

Damit bist du die Funktionspointer komplett los.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
>
> typedef void (* MyClass::funcPtr)( void );
> 

das ist Unsinn.
Die richtige Syntax ist
typedef void (MyClass::*funcPtr)( void );

Das mag zeigen, dass Zeiger auf Memberfunktionen in C++ so gut wie nie 
benutzt werden. Es gibt praktisch immer eine andere, bessere 
Möglichkeit.

(Alte C++ Weisheit: Jedes Problem kann mit einer zusätzlichen Klasse 
mehr gelöst werden :-)

Autor: P. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:

> Das mag zeigen, dass Zeiger auf Memberfunktionen in C++ so gut wie nie
> benutzt werden.

Ich habe das tatsaechlich nur einmal in 20 Jahren gebraucht und zwar um 
C++-Methoden fuer X11-Callbacks zu benutzen, ohne statische 
Proxy-Funktionen zu verwenden.

Autor: Peter Kröner (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich hätte nicht geahnt, das ich hier auf ein "geht nicht" in C++ stoße. 
Aber das Problem lässt sich ja umgehen. Danke euch!

Autor: Peter Kröner (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Bei der Variante von  Karl heinz Buchegger kann man dann ja nur eine 
Callback-Funktion pro Klasse definieren. Fällt euch was dazu ein, wie 
man das umgehen könnte?

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Kröner schrieb:
> Bei der Variante von  Karl heinz Buchegger kann man dann ja nur eine
> Callback-Funktion pro Klasse definieren. Fällt euch was dazu ein, wie
> man das umgehen könnte?

Innerhalb der Callback Funktion weiterverteilen?

Autor: Arc Net (arc)
Datum:

Bewertung
0 lesenswert
nicht lesenswert

Autor: Peter Kröner (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich meine, wenn ich in einer Klasse 2 Timer mit unterschiedlichen 
Intervallen verwenden möchte.


ok, dann kann man immer noch einen Timer mit höherer Frequenz nehmen, 
und dann in der Callbackfunktion mitzählen. Aber mir gehts im Moment 
darum, dass die Timerklasse so zu bauen, dass sie möglichst einfach und 
universell einsetzbar ist.

Autor: P. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Kröner schrieb:
> Ich meine, wenn ich in einer Klasse 2 Timer mit unterschiedlichen
> Intervallen verwenden möchte.

Proxyklassen verwenden, die den Callback an zwei verschieden benamste 
Callbacks in der Zielklasse weiterleiten. In Java geht das elegant und 
kurz mit Inner-Classes, ob das C++ schon geht, weiss ich im Moment gar 
nicht.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Kröner schrieb:
> Ich meine, wenn ich in einer Klasse 2 Timer mit unterschiedlichen
> Intervallen verwenden möchte.

Eine Möglichkeit ist, dass der jeweils den Callback auslösende Timer 
sich selbst als Argument mitgibt.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Stegemann schrieb:
> Peter Kröner schrieb:
>> Ich meine, wenn ich in einer Klasse 2 Timer mit unterschiedlichen
>> Intervallen verwenden möchte.
>
> Proxyklassen verwenden, die den Callback an zwei verschieden benamste
> Callbacks in der Zielklasse weiterleiten. In Java geht das elegant und
> kurz mit Inner-Classes, ob das C++ schon geht, weiss ich im Moment gar
> nicht.

Müsste gehen.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die call back-Funktion bekommt doch einen void*-Parameter.
Wie schon gesagt kann man das nutzen, um einer statischen
Methode, die man als call back angibt, einen Zeiger auf das
aktuelle Objekt mitgibt.
Diese statische Methode wiederum castet dann den Zeiger in
einen auf das Objekt und ruft eine normale Methode der Klasse
für dieses Objekt auf.

Das ist zwar etwas von hinten durch die Brust in den Tränensack,
aber so ist das nun mal, wenn man Nicht-OO wie eine
call back-Routine mit Objekten koppelt.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Stegemann schrieb:

> ...
> Proxyklassen verwenden, die den Callback an zwei verschieden benamste
> Callbacks in der Zielklasse weiterleiten. In Java geht das elegant und
> kurz mit Inner-Classes, ob das C++ schon geht, weiss ich im Moment gar
> nicht.

sischer dat

Autor: P. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:

> sischer dat

Dann spart man sich elegant die Pointerverknotung der Proxy-Klassen.

Autor: yalu (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich mache das immer so, oder so ähnlich:
#include <iostream>
using namespace std;

// Callback-Klasse für Memberfunktionen ohne Argumente und Rückgabewert
// (Die Signatur der Memberfunktion kann je nach bedarf geändert werden)

// Basisklasse (wird im Aufrufer des Callbacks benutzt)
class CallbackBase {
  public:
    virtual void operator()() = 0;
};

// abgeleitete Klasse (von dieser Klasse werden die Callback-Objekte
// instanziiert)
template <class C>
class Callback: public CallbackBase {
  public:
    Callback(C &obj, void (C::*method)()):
      m_obj(obj), m_method(method) {}
    virtual void operator()() { (m_obj.*m_method)(); }
  private:
    C &m_obj;  // Objekt, dessen Memberfunktion aufgerufen werden soll
    void (C::*m_method)();  // Zeiger auf die Memberfunktion
};


// Nun kommt ein Anwendungsbeispiel: 
class Timer {
  public:
    Timer(): m_pcb(NULL) {}
    void registerCallback(CallbackBase &cb) { m_pcb = &cb; }
    void useCallback() { if(m_pcb) (*m_pcb)(); }
  private:
    CallbackBase *m_pcb;
};

// Zwei Beispielklassen, von denen eine Memberfunktion aufgerufen werden soll
// Beide Klassen sind der Timer-Klasse unbekannt.
class Test1 {
  public:
    Test1(int val): m_val(val) {}
    void show() { cout << "Test1: " << m_val << endl; }
  private:
    int m_val;
};

class Test2 {
  public:
    Test2(int val): m_val(val) {}
    void show() { cout << "Test2: " << m_val << endl; }
  private:
    int m_val;
};

int main() {
  Timer timer;

  // Je zwei Instanzen der beiden Beispielklassen
  Test1 t11(111), t12(222);
  Test2 t21(111), t22(222);

  // Für jedes Beispielobjekt ein Callback-Objekt auf die memberfunktion 'show'
  Callback<Test1> cb11(t11, &Test1::show), cb12(t12, &Test1::show);
  Callback<Test2> cb21(t21, &Test2::show), cb22(t22, &Test2::show);

  // Registrieren der vier Callback-Objekte und schauen, was passiert
  timer.registerCallback(cb11);
  timer.useCallback();

  timer.registerCallback(cb12);
  timer.useCallback();

  timer.registerCallback(cb21);
  timer.useCallback();

  timer.registerCallback(cb22);
  timer.useCallback();

  return 0;
}

Das Ganze ist typsicher und erlaubt trotzdem eine saubere Trennung
zwischen Aufrufer und Aufgerufenem.

Autor: Peter Kröner (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja, das mit der Template-Klasse, die den Aufruf über eine überladene 
virtuelle Methode weiterreicht, ist genial!

Damit hab ich die volle Flexibilität, wie ich sie mir am Anfang mit den 
Funktionspointern vorgestellt hab. Typsicher und sauber getrennt ist es 
auch, was will man mehr!


C++ is manchmal gar nicht so einfach. Auf sowas wäre ich nie gekommen. 
Wieder was gelernt ;)  Danke an alle!

Autor: P. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
yalu schrieb:

> Das Ganze ist typsicher und erlaubt trotzdem eine saubere Trennung
> zwischen Aufrufer und Aufgerufenem.

Der Code ist extrem muehsam zu lesen. Musst du fuer Zeilenumbrueche 
extra bezahlen?

Autor: yalu (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Stegemann schrieb:

> Der Code ist extrem muehsam zu lesen. Musst du fuer Zeilenumbrueche
> extra bezahlen?

Stimmt, jetzt seh' ich's auch ;-)

Vor allem bei diesen Zeilen
  Callback<Test1> cb11(t11, &Test1::show), cb12(t12, &Test1::show);
  Callback<Test2> cb21(t21, &Test2::show), cb22(t22, &Test2::show);

fällt es sogar mir nicht leicht, die Einzelteile visuell zu gruppieren.
Bitte entschuldige meine schlampige Schreibweise. Das Ding ist in
leichter Eile und inkrementell entstanden. Ich werde mir beim nächsten
Mal aber mehr Mühe geben.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
hm, ich hätte nix zu meckern an der Optik.
Ist vielleicht auch ein Stück weit Geschmackssache?
Zumal es auch inhaltlich weiter bringt.

Autor: P. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> hm, ich hätte nix zu meckern an der Optik.
> Ist vielleicht auch ein Stück weit Geschmackssache?

Das Argument wird immer gerne gebracht. "Live" lasse ich mir dann 
normalerweise mal die "paar Zeilen" erklaeren - da aendert sich der 
Geschmack dann meist schnell.

Autor: Niklas Gürtler (erlkoenig)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jetzt brauchst du noch eine weitere von CallbackBase abgeleitete Klasse 
für statische/globale Funktionen, und der Unterschied zwischen 
Member-Funktionen und statischen Funktionen verschwindet ganz.
Wenn du noch ein paar Templates für Parametertypen und Rückgabewerte 
hinzufügst hast du libsigc (http://libsigc.sourceforge.net/) neu 
implementiert.

Autor: Schtiefel (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielen Dank .. hab ewig nach einem gut erklärten Beispiel gesucht, denn 
das meiste, was man findet ist nicht unbedingt spontan verständlich.
Doch ich denke jetzt hab ichs soweit - zumindest funktioniert es.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.