mikrocontroller.net

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


Autor: Hermi (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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
Autor: Borislav Bertoldi (boris_b)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Forum: Projekte & Code

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

Autor: Bernhard R. (bernhard_r28)
Datum:

Bewertung
0 lesenswert
nicht 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...

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

Gruß Bernhard

Autor: Torsten Robitzki (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht 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:
#include <iterator>
#include <iostream>

struct menu_item
{
    char key;
    void (*function)();
};

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

static const menu_item menu[] = {
    { 'a', a_pressed },
    { 'b', b_pressed },
    { 'c', c_pressed }
};

int main()
{
    char key;

    do
    {
        std::cin >> key;

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

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

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


Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Hermi (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.
class CommFunct1 {
  public unsigned char init( int parameterNum ){
  // code
  }
};
class CommFunct2 {
  public unsigned char init( int parameterNum ){
  // code
  }
};
class CommFunct3 {
  public unsigned char init( int parameterNum ){
  // code
  }
};

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

Autor: Torsten Robitzki (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Hermi (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
class CommFunctEltern {
  public unsigned char init( int parameterNum ){
  // code
  }
};
class CommFunct1 : CommFunctEltern {
  public unsigned char init( int parameterNum ){
  // code
  }
};
class CommFunct2 : CommFunctEltern {
  public unsigned char init( int parameterNum ){
  // code
  }
};
class CommFunct3 : CommFunctEltern {
  public unsigned char init( int parameterNum ){
  // code
  }
};

Autor: Torsten Robitzki (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hermi schrieb:
> hast du ein Code-Beispiel dazu wenn die Klassen so aussehen:
#include <iterator>
#include <iostream>
#include <algorithm>

class CommFunctEltern {
public:
   virtual unsigned char init( int parameterNum ) = 0;
protected:
    ~CommFunctEltern() {}
};

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

        return 0;
    }
};

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

        return 0;
    }
};

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

        return 0;
    }
};

static CommFunct1 func1;
static CommFunct2 func2;
static CommFunct3 func3;

struct menu_item
{
    char key;
    CommFunctEltern* function;
};

static const menu_item menu[] = {
    { 'a', &func1 },
    { 'b', &func2 },
    { 'c', &func3 }
};

int main()
{
    char key;

    do
    {
        std::cin >> key;

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

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

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

P.S. in C++ hat man Konstruktoren für die Initialisierung.

: Bearbeitet durch User
Autor: Hermi (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Torsten Robitzki (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Hermi (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
class CommFunct {
  public:
    CommFunct() { }
    virtual unsigned char init( int parameterNum ) { return( 0 );}
    virtual void loop() { }
    virtual char * getParametername( unsigned int parameterNummer ) { return( NULL );}
    virtual unsigned char getParametertyp( unsigned int parameterNummer ) { return( 0 );}
    virtual unsigned char getParameterAdds( unsigned int parameterNummer, unsigned char * buffer ) { return( 0 );}
  protected:
    ~CommFunct() {};

};

Und die test-Umgebung
class Testmodul : CommFunct {
  private:
    virtual unsigned char init( int parameterNum ) override {
        return( 0 );
    }
};

static Testmodul Tm;

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

struct menu_item
{
    char key;
    CommFunct* function;
};

static const menu_item menu[] = {
    { 'a', &Tm }          // hier ist kommt die Fehlermeldung
};

Autor: Torsten Robitzki (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hermi schrieb:
> Und die test-Umgebung
>
> class Testmodul : CommFunct {
> 

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:
class Testmodul : public CommFunct {

: Bearbeitet durch User
Autor: Hermi (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Guido (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: MaWin (Gast)
Datum:

Bewertung
1 lesenswert
nicht 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.

Autor: Hermi (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.