Forum: PC-Programmierung Callback Functions mit c++Klassen


von Hermi (Gast)


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.

: Verschoben durch Moderator
von Borislav B. (boris_b)


Lesenswert?

Forum: Projekte & Code

Hier könnt ihr eure Projekte, Schaltungen oder Codeschnipsel vorstellen 
und diskutieren. Bitte hier keine Fragen posten!

von Bernhard R. (bernhard_r28)


Lesenswert?

Ja, Projekte und Code ist wohl nicht ganz korrekt. Das kann ein Mod 
sicher noch verschieben.

Eventuell könnte dir das Observer Design Pattern weiterhelfen.

http://www.philipphauer.de/study/se/design-pattern/observer.php

In diesem Beispiel wäre der Zeitungsverlag dein Menü und die Abonnenten 
wären deine Funktionsklassen.

Gruß Bernhard

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


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
}

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Hermi (Gast)


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. :-)

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


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?

von Hermi (Gast)


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
};

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


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.

: Bearbeitet durch User
von Hermi (Gast)


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.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Hermi (Gast)


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
};

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hermi schrieb:
> Und die test-Umgebung
>
1
> class Testmodul : CommFunct {
2
>

default inheritance in C++ ist für classes private. CommFunct ist also 
eine private Basisklasse. Damit kann niemand (ausser Testmodul selbst) 
ein Testmodul da einsetzen, wo ein CommFunct gefordert ist. Du möchtest:
1
class Testmodul : public CommFunct {

: Bearbeitet durch User
von Hermi (Gast)


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.

von Guido (Gast)


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

von MaWin (Gast)


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.

von Hermi (Gast)


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.

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.