Funktionspointer für C++ Memberfunktion

Gast #1461340
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.
#1461354
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.
#1461373
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 :-)
Gast #1461409
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.
Gast #1461456
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.
Gast #1461470
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.
#1461476
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.
#1461482
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.
#1461485
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
Gast #1461547
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.
Gast #1461614
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!
Gast #1461831
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?
Gast #1461871
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.
Gast #1461896
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.
Gast #2576012
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.
Gast #4626330
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 ?
Gast #4627406
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
Gast #4628024
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)) {}

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren