Callback-Funktionen in C++

Gast #5110833
Lesenswert?

Ich habe eine  poll-Funktion, die auf Events wartet. Die ausgelösten 
Events (z.B. Zeit, Signal, etc..) Prüfe ich über if/else, also ob Signal 
oder Zeit ausgelöst wurden. Bei hunderten von Events ist das keine 
sinnvolle Lösung. Daher dachte ich an Callbackfunktionen, denen ich 
meine Event-Struktur bzw. die Adresse davon übergebe und dann über 
Callbacks die Abfrage umsetze.
Gast #5110869
Lesenswert?

Naja dann übergib halt in dem Struct einen Funktionszeiger und führ die 
Funktion aus. Allerdings ist das die Art, wie man in C objektorientiert 
programmiert. Man kann das auch in C++ machen, aber wenn man C++ nutzt, 
um damit ein C-artiges Objektsystem zu bauen, stimmt etwas grundsätzlich 
mit dem Design nicht.
#5110873
Lesenswert?

In C++ wäre die Lösung eine abstracte Klasse zu definieren, welche die 
"Callback"-Funktion als virtuelle Methode entält.

z.B. so:
1
class SomeEventListener
2
{
3
public:
4
  virtual void OnSomeEvent(const SomeEventArgs& args) = 0;
5
};
6

7
class SomeEventSource
8
{
9
public:
10
  void AddListener(SomeEventListener& listener);
11
  void RemoveListener(SomeEventListener& listener);
12

13
  /* ... */ 
14
}
15

16
class MyClassThatListensToSomeEvent : public SomeEventListener
17
{
18
public:
19
  virtual void OnSomeEvent(const SomeEventArgs& args)
20
  {
21
    /* Do stuff */
22
  }
23
}
(Firma: privatfrickler.de) #5110884
Lesenswert?

Hallo,

ähm, könnte vielleicht noch jemand erläutern, warum die Antwort von 
Sebastian E. downgevoted wird?

Das ist doch das klassische GoF Observer Pattern, das brutalst weit 
verbreitet ist? Zur Frage des TO passt es auch, zumindest liegen keine 
objektiven Befunde vor, die vermuten lassen, es passe nicht?

Vlg
 Timm
(Firma: keine) #5111021
Lesenswert?

Timm R. schrieb:
> Hallo,
>
> ähm, könnte vielleicht noch jemand erläutern, warum die Antwort von
> Sebastian E. downgevoted wird?
>
> Das ist doch das klassische GoF Observer Pattern, das brutalst weit
> verbreitet ist? Zur Frage des TO passt es auch, zumindest liegen keine
> objektiven Befunde vor, die vermuten lassen, es passe nicht?
>
> Vlg
>  Timm

Hab ich mich gefragt, ich hatte eine fast identische Antwort schon 
absendefertig, aber Sebastian war schneller...
(Firma: Torrox.de) #5111492
Lesenswert?

Timm R. schrieb:
> ähm, könnte vielleicht noch jemand erläutern, warum die Antwort von
> Sebastian E. downgevoted wird?

Ich war es nicht. Ich könnte mir vorstellen, dass andere die Lösung zu 
kompliziert finden, wenn nach Callback-_Funktion_ gefragt wurde. Oder 
vielleicht war es der Camel Case Java-Style des Beispiels ;-)

Ich mag das Observer-Pattern auch sehr gerne, auch wenn Vererbung eine 
ziemlich starke Kopplung ist. Als "Verbesserungsvorschlag" zur Lösung 
von Sebastian: Man kann das Interface auf privat erben, wenn man z.B. 
den c'tor der Implementierung das Registrieren überläßt.

In modernem C++ würde ich vielleicht die deutlich losere Kopplung über 
std::function wählen und insbesondere im Embedded Bereich versuchen, 
dass ganze zur Compile-Zeit aufzulösen (sprich: Curiously recurring 
template pattern). Das hängt aber alles ein wenig von den nicht 
bekannten Anforderunen ab.
(Firma: privatfrickler.de) #5111616
Lesenswert?

Hallo Torsten,

ich bin im Moment noch sehr rückständig, was aktuelles C++ angeht, mein 
letztes ernsthaftes C++ war in den 90ern :-) Bin aber dabei mich zu 
modernisieren und hätte daher ein paar Nachfragen:

Torsten R. schrieb:

> In modernem C++ würde ich vielleicht die deutlich losere Kopplung über
> std::function wählen und insbesondere im Embedded Bereich versuchen,
> dass ganze zur Compile-Zeit aufzulösen (sprich: Curiously recurring
> template pattern). Das hängt aber alles ein wenig von den nicht
> bekannten Anforderunen ab.

wenn man den Callback als template Parameter realisieren würde, hätte 
man ja als Vorteil, dass man sowohl Funktionszeiger, als auch einen 
Funktor / Funktionsobjekt oder ein lambda als Callback verwenden kann ?

Was ja nicht alles bei std::function gehen würde. Außerdem könnte der 
Callback ge-inlined werden, was bei std::function auch nicht gehen würde 
?

Aber, was mir nicht ganz klar ist, wo das crtp ins Spiel kommt? Die 
Implementierung würde doch so ähnlich aussehen?
1
template <typename cf>
2
void blabla(..., cf&& callback) {
3
    ...
4
    callback(...);
5
    ...
6
}

Wo kommt denn crtp ins Spiel?

vlg

 Timm
Moderator #5111637
Lesenswert?

Timm R. schrieb:
> wenn man den Callback als template Parameter realisieren würde, hätte
> man ja als Vorteil, dass man sowohl Funktionszeiger, als auch einen
> Funktor / Funktionsobjekt oder ein lambda als Callback verwenden kann ?

std::function macht doch genau das:

1
#include <iostream>
2
#include <functional>
3

4
void sub(std::function<int(int)> f) {
5
  std::cout << f(10) << '\n';
6
}
7

8
int f1(int x) {
9
  return x + 1;
10
}
11

12
struct C {
13
  int operator()(int x) { return 2 * x; }
14
};
15

16
int main() {
17
  sub(f1);                            // Funktionszeiger
18
  sub(C());                           // callable Object
19
  sub([] (int x) { return x * x; });  // Lambda
20
}
(Firma: Torrox.de) #5111660
Lesenswert?

Timm R. schrieb:
> Wo kommt denn crtp ins Spiel?

Wenn Du Callbacks verwenden möchtest, um die Kopplung zwischen Modulen 
zu reduzieren, dann kannst Du anstatt von Laufzeit-Polymorphie (Dein 
Vorschlag) auch Compilezeit-Polymorphie verwenden:
1
class MyClassThatListensToSomeEvent
2
{
3
public:
4
    void OnSomeEvent(const SomeEventArgs& args);
5
};
6

7
template < class Listener >
8
class SomeEventSource : public Listener
9
{
10
public:
11
    void EventFired()
12
    {
13
        static_cast< *MyClassThatListensToSomeEvent >( this )->OnSomeEvent( SomeEventArgs{ ... } );
14
    }
15
}

Es kommt halt darauf an, warum der OP nach Callback-Functions fragt. 
Ggf. wäre sogar Zeiger auf Member Functions die richtige Lösung für sein 
Problem (aber das hat er uns ja nicht verraten).
Gast #5111784
Lesenswert?

Callbacks folgen ja der Syntax:
1
int foobar (int x, int (*moo)(int))
2
{
3
    return x + moo(x); // function pointer moo called using argument x
4
}
5
// analog
6
int foobar (int x, f_int_t moo)
7
{
8
    return x + moo(x); // function pointer moo called using argument x
9
}

Ich kenne lediglich Funktionszeiger, aber eben mit folgender Syntax:
1
int addiere (int x, int y) {return x+y;}
2
int (*pFunktion) (int, int);
3

4
int main(){
5
pFunktion = addiere;
6
pFunktion(100, 200);
7
return 0;
8
}

Sind beide Codes ein und dasselbe??
(Firma: privatfrickler.de) #5111790
Lesenswert?

Hallo Torsten,

darf ich noch eine letzte Nachfrage stellen?


Torsten R. schrieb:

> Wenn Du Callbacks verwenden möchtest, um die Kopplung zwischen Modulen
> zu reduzieren, dann kannst Du anstatt von Laufzeit-Polymorphie (Dein
> Vorschlag) auch Compilezeit-Polymorphie verwenden:

in „meinem“ Beispiel wird die Callback-Funktion geinlined, also in 
blabla(..., cf&& callback) eingesetzt, warum ist das dann 
Laufzeit-Polymorphie?

Versteh mich bitte nicht falsch, ich will nicht diskutieren, dazu habe 
ich auch gar nicht die Kenntnisse, es geht mir um Verständnis.

Vlg

 Timm
(Firma: privatfrickler.de) #5111981
Lesenswert?

Hallo Michael,

mal eine Antwort von Anfänger zu Anfänger:

Das ist alles schon etwas kryptisch!

Einerseits scheint dir ja klar zu sein, was Du erreichen willst:
> Ich habe eine  poll-Funktion, die auf Events wartet. Die ausgelösten
> Events (z.B. Zeit, Signal, etc..) Prüfe ich über if/else, also ob Signal
> oder Zeit ausgelöst wurden. Bei hunderten von Events ist das keine
> sinnvolle Lösung.

anderseits schreibst Du

> Daher dachte ich an Callbackfunktionen, denen ich
> meine Event-Struktur bzw. die Adresse davon übergebe und dann über
> Callbacks die Abfrage umsetze.

was sich extrem seltsam liest, oder Du fragst Dich, ob Callback das 
selbe ist, wie Rekursion.

Michael schrieb:
> Callbacks folgen ja der Syntax:
>
1
> int foobar (int x, int (*moo)(int))
2
> {
3
>     return x + moo(x); // function pointer moo called using argument x
4
> }
5
>

nein. Also ja, schon, aber nicht nur, der Callback ist aber „moo”.

> [x]// analog
> int foobar (int x, f_int_t moo)[/c]

was möchtest Du damit sagen? f_int_t gibt es nicht. Klar kann man das 
aliasen, aber was hat das mit Deinem Problem oder den Eigenschaften von 
Callbacks zu tun?

> Ich kenne lediglich Funktionszeiger, aber eben mit folgender Syntax:

>
1
> int addiere (int x, int y) {return x+y;}
2
> int (*pFunktion) (int, int);
3
> 
4
> int main(){
5
> pFunktion = addiere;
6
> pFunktion(100, 200);
7
> return 0;
8
> }
9
>
>
> Sind beide Codes ein und dasselbe??

Die Frage kann man vermutlich nicht entschlüsseln? Das beide Codes nicht 
identisch sind, sieht man ja. Aber beide verwenden Funktionszeiger. Dein 
erstes Beispiel auch.

Du hast ja jetzt insgesamt vier (oder so) Varianten vorgeschlagen 
bekommen, wie Du einen Callback realisieren kannst. Du solltest 
vielleicht versuchen etwas exakter einzukreisen, wo das Problem liegt.

Bild: Ein Callback ist ein Rückruf, dazu benötigst Du ein Telefon 
(Funktionspointer o.ä.) aber deswegen ist nicht jedes Telefonat ein 
Rückruf.

Callback-Funktionen sind Funktionen, die anderen Funktionen als Argument 
übergeben werden und von diesen aufgerufen werden.

Dazu gibt es in C++ (soweit ich das überblicke) mehrere Möglichkeiten: 
Funktionspointer, den polymorphen Allzweck-Funktionswrapper 
std::function, verwendbar mit Funktionspointern, Funktionsobjekten und 
Lambdas, das observer Pattern mit Ableitung von einer abstrakten 
Basisklasse oder eben Template-Argumente.

Wobei sich für mein unmaßgebliches Bauchgefühl die 
Funktionspointer-Variante am wenigsten nach C++ anfühlt, wenn ich mich 
nicht irre, würde man für einen member function pointer sowohl den 
function pointer, als auch den Pointer auf das Object benötigen.

Wollte man also function pointers mit Klassen einsetzen, müsste 
vermutlich die member function static sein oder man müsste halt mit zwei 
Pointern arbeiten. Das wäre schon sehr gewollt.

Am performantesten wird vermutlich das Template-Argument sein, weil das 
geinlined werden kann.

vlg

 Timm
Beitrag #5137747 wurde von einem Moderator gelöscht.
Beitrag #5137780 wurde von einem Moderator gelöscht.
Beitrag #5137838 wurde von einem Moderator gelöscht.
Beitrag #5137945 wurde von einem Moderator gelöscht.
Beitrag #5138074 wurde von einem Moderator gelöscht.
Beitrag #5138358 wurde von einem Moderator gelöscht.
Beitrag #5138524 wurde von einem Moderator gelöscht.
Beitrag #5139165 wurde von einem Moderator gelöscht.
Beitrag #5139830 wurde von einem Moderator gelöscht.
Beitrag #5139979 wurde von einem Moderator gelöscht.
Beitrag #5140042 wurde von einem Moderator gelöscht.
Beitrag #5140093 wurde von einem Moderator gelöscht.
Beitrag #5140235 wurde von einem Moderator gelöscht.
Beitrag #5140377 wurde von einem Moderator gelöscht.
Beitrag #5140399 wurde von einem Moderator gelöscht.
Beitrag #5140402 wurde von einem Moderator gelöscht.
Beitrag #5140408 wurde von einem Moderator gelöscht.
Beitrag #5140409 wurde von einem Moderator gelöscht.
Beitrag #5140436 wurde von einem Moderator gelöscht.
Beitrag #5142594 wurde von einem Moderator gelöscht.
Beitrag #5143769 wurde von einem Moderator gelöscht.
Beitrag #5144963 wurde von einem Moderator gelöscht.
Beitrag #5144992 wurde von einem Moderator gelöscht.
Beitrag #5145004 wurde von einem Moderator gelöscht.
Beitrag #5145228 wurde von einem Moderator gelöscht.
Beitrag #5145802 wurde von einem Moderator gelöscht.
Beitrag #5145820 wurde von einem Moderator gelöscht.
Beitrag #5145936 wurde von einem Moderator gelöscht.
Beitrag #5145962 wurde von einem Moderator gelöscht.
Beitrag #5145973 wurde von einem Moderator gelöscht.
Beitrag #5148066 wurde von einem Moderator gelöscht.
Beitrag #5149295 wurde von einem Moderator gelöscht.
Beitrag #5149360 wurde von einem Moderator gelöscht.
Beitrag #5149381 wurde von einem Moderator gelöscht.
Beitrag #5149416 wurde von einem Moderator gelöscht.
Beitrag #5153206 wurde von einem Moderator gelöscht.
Beitrag #5153955 wurde von einem Moderator gelöscht.
Beitrag #5158698 wurde von einem Moderator gelöscht.
Beitrag #5159358 wurde von einem Moderator gelöscht.
Beitrag #5166158 wurde von einem Moderator gelöscht.
Beitrag #5166167 wurde von einem Moderator gelöscht.
Beitrag #5167775 wurde von einem Moderator gelöscht.
Beitrag #7197960 wurde von einem Moderator gelöscht.
Dieser Beitrag ist gesperrt und kann nicht beantwortet werden.