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


von Peter Kröner (Gast)


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:
1
void* object;
2
void (*callback)(void*);    // Kann man den so deklarieren? der 
3
                            // this-Pointer taucht ja eigentlich 
4
                            // in der Funktionssignatur gar nicht auf?
Callback Aufruf:
1
callback(object);  // wird nicht funktionieren, da ich ja das, was der
2
                   // This-Pointer sein soll, nicht explizit der Funktion 
3
                   // übergeben kann


Andere Möglichkeit:
1
void* object;
2
void (*callback)();
3
4
...
5
6
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.

von Rufus Τ. F. (rufus) Benutzerseite


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.

von Karl H. (kbuchegg)


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++

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

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.
1
class TimerCallback
2
{
3
  public:
4
    virtual void DoWork() = 0;
5
};

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.
1
class Timer
2
{
3
  public:
4
    Timer() : callBack ( NULL ) {}
5
6
    void RegisterCallback( TimerCallback* object );
7
    void PerformCallback()
8
    {
9
      if( callBack )
10
        callBack->DoWork();
11
    }
12
13
  protected:
14
    TimerCallback* callBack;
15
};
16
17
class MyClass : public MyBaseClass,
18
                public TimerCallback   // <- das verleiht MyClass die
19
                                       // Fähigkeit als Callbackobjekt
20
                                       // fuer Timer zu fungieren
21
{
22
  public:
23
    
24
    void Something( Timer& timer )
25
    {
26
      timer.RegisterCallback( this );
27
    }
28
29
    virtual void DoWork()   // der Compiler wacht darüber, dass diese Funktion
30
    {                       // auch tatsächlich implementiert wird
31
    }
32
};

Damit bist du die Funktionspointer komplett los.

von Karl H. (kbuchegg)


Lesenswert?

Karl heinz Buchegger schrieb:
>
1
> typedef void (* MyClass::funcPtr)( void );
2
>

das ist Unsinn.
Die richtige Syntax ist
1
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 :-)

von P. S. (Gast)


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.

von Peter Kröner (Gast)


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!

von Peter Kröner (Gast)


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?

von Karl H. (kbuchegg)


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?

von Arc N. (arc)


Lesenswert?


von Peter Kröner (Gast)


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.

von P. S. (Gast)


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.

von Karl H. (kbuchegg)


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.

von Karl H. (kbuchegg)


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.

von Klaus W. (mfgkw)


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.

von Klaus W. (mfgkw)


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

von P. S. (Gast)


Lesenswert?

Klaus Wachtler schrieb:

> sischer dat

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

von yalu (Gast)


Lesenswert?

Ich mache das immer so, oder so ähnlich:
1
#include <iostream>
2
using namespace std;
3
4
// Callback-Klasse für Memberfunktionen ohne Argumente und Rückgabewert
5
// (Die Signatur der Memberfunktion kann je nach bedarf geändert werden)
6
7
// Basisklasse (wird im Aufrufer des Callbacks benutzt)
8
class CallbackBase {
9
  public:
10
    virtual void operator()() = 0;
11
};
12
13
// abgeleitete Klasse (von dieser Klasse werden die Callback-Objekte
14
// instanziiert)
15
template <class C>
16
class Callback: public CallbackBase {
17
  public:
18
    Callback(C &obj, void (C::*method)()):
19
      m_obj(obj), m_method(method) {}
20
    virtual void operator()() { (m_obj.*m_method)(); }
21
  private:
22
    C &m_obj;  // Objekt, dessen Memberfunktion aufgerufen werden soll
23
    void (C::*m_method)();  // Zeiger auf die Memberfunktion
24
};
25
26
27
// Nun kommt ein Anwendungsbeispiel: 
28
class Timer {
29
  public:
30
    Timer(): m_pcb(NULL) {}
31
    void registerCallback(CallbackBase &cb) { m_pcb = &cb; }
32
    void useCallback() { if(m_pcb) (*m_pcb)(); }
33
  private:
34
    CallbackBase *m_pcb;
35
};
36
37
// Zwei Beispielklassen, von denen eine Memberfunktion aufgerufen werden soll
38
// Beide Klassen sind der Timer-Klasse unbekannt.
39
class Test1 {
40
  public:
41
    Test1(int val): m_val(val) {}
42
    void show() { cout << "Test1: " << m_val << endl; }
43
  private:
44
    int m_val;
45
};
46
47
class Test2 {
48
  public:
49
    Test2(int val): m_val(val) {}
50
    void show() { cout << "Test2: " << m_val << endl; }
51
  private:
52
    int m_val;
53
};
54
55
int main() {
56
  Timer timer;
57
58
  // Je zwei Instanzen der beiden Beispielklassen
59
  Test1 t11(111), t12(222);
60
  Test2 t21(111), t22(222);
61
62
  // Für jedes Beispielobjekt ein Callback-Objekt auf die memberfunktion 'show'
63
  Callback<Test1> cb11(t11, &Test1::show), cb12(t12, &Test1::show);
64
  Callback<Test2> cb21(t21, &Test2::show), cb22(t22, &Test2::show);
65
66
  // Registrieren der vier Callback-Objekte und schauen, was passiert
67
  timer.registerCallback(cb11);
68
  timer.useCallback();
69
70
  timer.registerCallback(cb12);
71
  timer.useCallback();
72
73
  timer.registerCallback(cb21);
74
  timer.useCallback();
75
76
  timer.registerCallback(cb22);
77
  timer.useCallback();
78
79
  return 0;
80
}

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

von Peter Kröner (Gast)


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!

von P. S. (Gast)


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?

von yalu (Gast)


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
1
  Callback<Test1> cb11(t11, &Test1::show), cb12(t12, &Test1::show);
2
  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.

von Klaus W. (mfgkw)


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.

von P. S. (Gast)


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.

von Niklas G. (erlkoenig) Benutzerseite


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.

von Schtiefel (Gast)


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.

von Sebi (Gast)


Lesenswert?

string(*psprache)(int n, string b);

int main()
{
  int n;
  string b, ivar, *pvar;


  cout << "Choose 1=deutsch 2=englisch 3=französisch 4=japanisch 
5=arabisch  :" ;
  cin >> n;
  cout << "What's your name ? ";
  cin >> b;

  if (n == 1) {

    psprache = *deutsch;
    cout << psprache;

  }

  system("Pause");
  return 0;
}


wie deklariere ich if richtig ?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Hat diese Frage auch nur irgendetwas mit dem Thema des jetzt auch 
schon vier Jahre alten Threads zu tun?

Und was soll "wie deklariere ich if" bedeuten? if ist nichts, was 
deklariert werden müsste, geschweige denn deklariert werden könnte.

von Klaus W. (mfgkw)


Lesenswert?

Ist ja eh nur eine Hausaufgabe

von Firlefanzfachverkäufer (Gast)


Lesenswert?

Natürlich geht sowas. Z.B mit std::function und std::bind
1
#include <functional>
2
#include <iostream>
3
4
class Timer
5
{
6
  std::function<void()> _f;
7
public:
8
  Timer(std::function<void()> && f) : _f(f) {}
9
  void mach()
10
  {
11
    _f();
12
  }
13
};
14
15
class X
16
{
17
public:
18
  int i;
19
  void function()
20
  {
21
    std::cout << "X: " << i << '\n';
22
  }
23
};
24
25
void fun()
26
{
27
  std::cout << "fun()\n";
28
}
29
30
int main()
31
{
32
  X x;
33
  x.i = 23;
34
  X x2;
35
  x2.i = 4711;
36
  Timer t(std::bind(&X::function, &x));
37
  Timer t2(std::bind(&X::function, &x2));
38
  Timer t3(&fun);
39
  t.mach();
40
  t2.mach();
41
  t3.mach();
42
}

Ausgabe:
1
X: 23
2
X: 4711
3
fun()

http://ideone.com/ItU0W1

von tictactoe (Gast)


Lesenswert?

Gut, dass du std::function ins Spiel bringst. So ist es richtig. In 
modernem C++ wird man dein main() aber mit Lambda-Ausdrücken schreiben 
wollen:
1
int main()
2
{
3
  X x;
4
  x.i = 23;
5
  X x2;
6
  x2.i = 4711;
7
  Timer t([&]() { x.function(); });
8
  Timer t2([&]() { x2.function(); });
9
  Timer t3(&fun);   // evtl auch hier, aber ich bin mir nicht sicher
10
  t.mach();
11
  t2.mach();
12
  t3.mach();
13
}
Der Grund ist, dass std::function schon einen indirekten Funktionsaufruf 
enthält, und dann das std::bind-Konstrukt einen weiteren. Mit dem 
Lambda-Ausdruck kann man diesen zweiten vermeiden. Im Gegenzug muss man 
der Timer-Klasse einen template-Konstruktor verpassen:
1
  template<class CB>
2
  Timer(CB && f) : _f(std::forward<CB>(f)) {}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.