Callback Functions mit c++Klassen

Gast #4708073
Lesenswert?

Hallo zusammen,

ich versuche auf meinem M4-Cortex je nach Menü-Einstellung 
unterschiedliche Funktionalitäten darzustellen.

Bisher habe ich es so realisiert, dass ich je eine eigene Klasse für die 
Funktionalität erstellt habe. Dort gibt es immer wieder die gleichen 
Methoden jedoch mit unterschiedlichem Funktionsinhalt.

Aufgerufen wurde das Ganze, von einer "Steuer"-Klasse mit einer ewig 
langen SWITCH/CASE-Wüste. Und das jeweils für die 8 verschiedenen 
Aufrufe.
Nun muss diese SWITCH/CASE-Wüste jedesmal angepasst werden, wenn ich 
eine neue Funktionalität einbauen möchte. Und ich möchte viele Versionen 
mit unterschiedlicher Zusammenstellung erstellen.

Nun dachte ich mir, es wäre einfacher alles über ein Array-of-functions 
zu steuern. Einfach statt der SWITCH/CASE einen Aufruf mit dem 
entsprechenden Index.

Nur wie bekomme ich die Klassen bzw. die Methoden in ein Array? Und wie 
kann ich sie dann aufrufen?

Ich hab mir schon eine Klasse mit virtuelle Methoden für die 
Array-Definition gebaut, komm aber damit nicht weiter.

Hab nun schon einige Stunden zusammen mit Tante Google verschiedene 
Sachen ausprobiert. Nix wollte funktionieren.
(Firma: Torrox.de) #4708099
Lesenswert?

Hermi schrieb:
> Nur wie bekomme ich die Klassen bzw. die Methoden in ein Array? Und wie
> kann ich sie dann aufrufen?

Wenn die Funktionen keine Parameter haben, reichen in der Regel function 
pointer:
1
#include <iterator>
2
#include <iostream>
3

4
struct menu_item
5
{
6
    char key;
7
    void (*function)();
8
};
9

10
void a_pressed() { std::cout << "A"; }
11
void b_pressed() { std::cout << "B"; }
12
void c_pressed() { std::cout << "C"; }
13

14
static const menu_item menu[] = {
15
    { 'a', a_pressed },
16
    { 'b', b_pressed },
17
    { 'c', c_pressed }
18
};
19

20
int main()
21
{
22
    char key;
23

24
    do
25
    {
26
        std::cin >> key;
27

28
        const auto wahl = std::find_if( std::begin( menu ), std::end( menu ),
29
            [key]( const menu_item& m ) -> bool {
30
                return key == m.key;
31
            }
32
        );
33

34
        if ( wahl != std::end( menu ) )
35
            wahl->function();
36

37
    } while ( key != 'q' );
38
}
Gast #4708183
Lesenswert?

Tut mir leid, wenn's in falsche Forum gerutscht ist. Welches wäre denn 
das richtige?
@Admin: bitte dahin verschieben. Danke.

Bernhard R. schrieb:
> Eventuell könnte dir das Observer Design Pattern weiterhelfen.

Das denke ich ist etwas übers Ziel hinausgeschossen. Es soll ja auch 
noch relativ Speichersparend bleiben. Ist ja "nur" ein M4. Und geht das 
in C++ überhaupt?

Torsten R. schrieb:
> Wenn die Funktionen keine Parameter haben, reichen in der Regel function
> pointer

Soweit richtig. Das funktioniert bei mir auch. Sogar mit Parameter, wenn 
alle die gleichen Parameter haben.
Nur hagelt es Fehlermeldungen wenn die Funktionen in den verschiedenen 
Klassen versteckt sind.
1
class CommFunct1 {
2
  public unsigned char init( int parameterNum ){
3
  // code
4
  }
5
};
6
class CommFunct2 {
7
  public unsigned char init( int parameterNum ){
8
  // code
9
  }
10
};
11
class CommFunct3 {
12
  public unsigned char init( int parameterNum ){
13
  // code
14
  }
15
};

Yalu X. schrieb:
> Wenn die einzelnen Klassen von einer gemeinsamen Basisklasse abgeleitet
> sind und die gleichartigen aufzurufenden Methoden virtuell sind, sollte
> das auch ohne switch und ohne explizite Funktionszeiger gehen.

Und wie? Genau das war meine Frage. :-)
(Firma: Torrox.de) #4708202
Lesenswert?

Hermi schrieb:
> Und wie? Genau das war meine Frage. :-)

In dem Du (im obigen Beispiel) die Zeiger auf Funktionen durch Zeiger 
auf Instanzen der von der Basisklasse abgeleiteten Klassen ersetzt. 
Alternativ könntest Du auch mit std::function<> alles auf einen Typen 
bringen, allerdings kann es dann sein, dass std::function<> dynamischen 
Speicher verwendet, was für Deinen Anwendungsfall eigentlich nicht nötig 
ist.

Warum möchtest Du einen Funktion durch eine Klasse abstrahieren? Haben 
die "Funktionen" einen Zustand, der zwischen zwei Aufrufen erhalten 
bleiben muss? Wenn ja, müssen sich dann nicht evtl. sogar mehrere 
Funktionen den selben Zustand teilen?
Gast #4708231
Lesenswert?

Torsten R. schrieb:
> Warum möchtest Du einen Funktion durch eine Klasse abstrahieren? Haben
> die "Funktionen" einen Zustand, der zwischen zwei Aufrufen erhalten
> bleiben muss? Wenn ja, müssen sich dann nicht evtl. sogar mehrere
> Funktionen den selben Zustand teilen?

Die Klassen haben insgesamt 5 Funktionen. Um die Übersichtlichkeit zu 
bewahren, haben alle die selben Methodennamen. Die Zustände sollen 
zwischen den Aufrufen erhalten bleiben und evtl auch zwischen den 
Funktionen einer Klasse geshared werden. Aber immer nur innerhalb der 
Klasse.
Alle übergeordneten Aktionen werden von der Steuerklasse über die 
Rückgabewerte und Parameter ausgeführt.

Torsten R. schrieb:
> Zeiger
> auf Instanzen der von der Basisklasse abgeleiteten Klassen ersetzt

hast du ein Code-Beispiel dazu wenn die Klassen so aussehen:
1
class CommFunctEltern {
2
  public unsigned char init( int parameterNum ){
3
  // code
4
  }
5
};
6
class CommFunct1 : CommFunctEltern {
7
  public unsigned char init( int parameterNum ){
8
  // code
9
  }
10
};
11
class CommFunct2 : CommFunctEltern {
12
  public unsigned char init( int parameterNum ){
13
  // code
14
  }
15
};
16
class CommFunct3 : CommFunctEltern {
17
  public unsigned char init( int parameterNum ){
18
  // code
19
  }
20
};
(Firma: Torrox.de) #4708238
Lesenswert?

Hermi schrieb:
> hast du ein Code-Beispiel dazu wenn die Klassen so aussehen:
1
#include <iterator>
2
#include <iostream>
3
#include <algorithm>
4

5
class CommFunctEltern {
6
public:
7
   virtual unsigned char init( int parameterNum ) = 0;
8
protected:
9
    ~CommFunctEltern() {}
10
};
11

12
class CommFunct1 : public CommFunctEltern {
13
private:
14
    virtual unsigned char init( int parameterNum ) override {
15
        std::cout << "CommFunct1" << std::endl;
16

17
        return 0;
18
    }
19
};
20

21
class CommFunct2 : public CommFunctEltern {
22
private:
23
    virtual unsigned char init( int parameterNum ) override {
24
        std::cout << "CommFunct2" << std::endl;
25

26
        return 0;
27
    }
28
};
29

30
class CommFunct3 : public CommFunctEltern {
31
private:
32
    virtual unsigned char init( int parameterNum ) override {
33
        std::cout << "CommFunct3" << std::endl;
34

35
        return 0;
36
    }
37
};
38

39
static CommFunct1 func1;
40
static CommFunct2 func2;
41
static CommFunct3 func3;
42

43
struct menu_item
44
{
45
    char key;
46
    CommFunctEltern* function;
47
};
48

49
static const menu_item menu[] = {
50
    { 'a', &func1 },
51
    { 'b', &func2 },
52
    { 'c', &func3 }
53
};
54

55
int main()
56
{
57
    char key;
58

59
    do
60
    {
61
        std::cin >> key;
62

63
        const auto wahl = std::find_if( std::begin( menu ), std::end( menu ),
64
            [key]( const menu_item& m ) -> bool {
65
                return key == m.key;
66
            }
67
        );
68

69
        if ( wahl != std::end( menu ) )
70
            wahl->function->init( 1 );
71

72
    } while ( key != 'q' );
73
}

P.S. in C++ hat man Konstruktoren für die Initialisierung.
Gast #4708315
Lesenswert?

Da bekomme ich folgende Fehlermeldung:
Error: Conversion to inaccessible base class "CommFunctEltern" is not 
allowed in "main.cpp"

Torsten R. schrieb:
> P.S. in C++ hat man Konstruktoren für die Initialisierung.

Mein init hat nix mit der Klasseninitiatilisierung zu tun, sondern mit 
der funktionsgesteuerten Hardwareinitialisierung.
Gast #4708357
Lesenswert?

Torsten R. schrieb:
> Hermi schrieb:
>> Da bekomme ich folgende Fehlermeldung:
>> Error: Conversion to inaccessible base class "CommFunctEltern" is not
>> allowed in "main.cpp"
>
> In welcher Zeile sollte das sein? Dass könnte nur der Fall sein, wenn Du
> (wie in Deinem Beispiel) weiterhin private von der Basis-Klasse erbst.

Es ist in der Zeile, in der die Zuweisung des Klassenobjekts in die 
struct erfolgt.

Ich steh jetzt etwas auf der Leitung. Welche private vererbe ich.

Ich hab mal die Echte Klassenbeschreibung hier:
1
class CommFunct {
2
  public:
3
    CommFunct() { }
4
    virtual unsigned char init( int parameterNum ) { return( 0 );}
5
    virtual void loop() { }
6
    virtual char * getParametername( unsigned int parameterNummer ) { return( NULL );}
7
    virtual unsigned char getParametertyp( unsigned int parameterNummer ) { return( 0 );}
8
    virtual unsigned char getParameterAdds( unsigned int parameterNummer, unsigned char * buffer ) { return( 0 );}
9
  protected:
10
    ~CommFunct() {};
11

12
};

Und die test-Umgebung
1
class Testmodul : CommFunct {
2
  private:
3
    virtual unsigned char init( int parameterNum ) override {
4
        return( 0 );
5
    }
6
};
7

8
static Testmodul Tm;
9

10
//CommFunct *funcArr[] = { &Tm };
11

12
struct menu_item
13
{
14
    char key;
15
    CommFunct* function;
16
};
17

18
static const menu_item menu[] = {
19
    { 'a', &Tm }          // hier ist kommt die Fehlermeldung
20
};
Gast #4708455
Lesenswert?

Danke!

So läufts. Again what learned

Ich hab das ganze noch als Array aufgesetzt statt des struct. Damit spar 
ich mir die Auswahlschleife. Von der Auswahlfunktion kommt sowieso eine 
fortlaufende Nummer als uint8 zurück.
Gast #4708612
Lesenswert?

Hermi schrieb:
> Ich hab das ganze noch als Array aufgesetzt statt des struct. Damit spar
> ich mir die Auswahlschleife. Von der Auswahlfunktion kommt sowieso eine
> fortlaufende Nummer als uint8 zurück.

Ich überlege gerade, ob es Sinn macht, wenn deine Funktionsklassen
ihren Callback bei der Steuer-Klasse anmelden. Dann müsstest du
beim Hinzufügen neuer Funktionen nur die Funktionsklasse schreiben, 
alles andere wäre sozusagen generisch implementiert. Nur so ein Gedanke, 
ich hab jetzt auch nicht den ganzen Thread gelesen, weiß auch nicht, ob 
das mit
anderen Konzepten bei dir kollidiert...
Gast #4708743
Lesenswert?

Hermi schrieb:
> Ich hab mir schon eine Klasse mit virtuelle Methoden für die
> Array-Definition gebaut, komm aber damit nicht weiter.

Du solltest die Steuer-Klasse mit virtuellen Methoden bauen, dann ist 
die vtable dein Array, ganz versteckt.

Callback nennt man solche Funktionen aber nicht.

Eine Callback-Funktion kommt von woanders. Ihre Adresse wird dorthin 
übergeben. Damit man nicht bloss eine C Funktion sondern eine C++ 
Memberfunktion aufrufen willst, dann brauch man zusätzlich den self 
Pointer, den ma  neben der callback Funktionsadresse auch übergeben 
müstte.
Gast #4709210
Lesenswert?

Guido schrieb:
> Ich überlege gerade, ob es Sinn macht, wenn deine Funktionsklassen
> ihren Callback bei der Steuer-Klasse anmelden. Dann müsstest du
> beim Hinzufügen neuer Funktionen nur die Funktionsklasse schreiben,
> alles andere wäre sozusagen generisch implementiert. Nur so ein Gedanke,
> ich hab jetzt auch nicht den ganzen Thread gelesen, weiß auch nicht, ob
> das mit
> anderen Konzepten bei dir kollidiert...

Da das ganze auf einem Arm-M4-Cortex-Controller läuft und alles bei 
Compilezeit definiert ist, wäre das Anmelden der Funktion nur zusätzlich 
Code-Speicher-Ausführungszeit.

Leider finde ich die Platzierung des Beitrags im PC-Programmierungsforum 
etwas unglücklich. Aber soll wohl so sein.

MaWin schrieb:
> Callback nennt man solche Funktionen aber nicht.

Gut, aber wie heißts dann?

MaWin schrieb:
> Hermi schrieb:
>> Ich hab mir schon eine Klasse mit virtuelle Methoden für die
>> Array-Definition gebaut, komm aber damit nicht weiter.
>
> Du solltest die Steuer-Klasse mit virtuellen Methoden bauen, dann ist
> die vtable dein Array, ganz versteckt.

Ich denk, mein Weg war nicht ganz verkehrt. Nur leider mangels 
Wissen/Erfahrung nicht ganz fertig. Dank Torsten Robitzki läufts jetzt 
und ist auch relativ schlank.
Ich habe meine Funktionen in eigenen abgeschlossenen Klassen/Files, Die 
Steuerklasse ist auch schön separiert und ich kann alles, nach Bedarf, 
in meinem main.cpp zusammennageln.

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