Forum: PC-Programmierung Class method pointer


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Reginald L. (Firma: HS Ulm) (reggie)


Bewertung
-1 lesenswert
nicht lesenswert
Huhu!

ich versuche ein Pointer-Array zu erstellen, welche verschiedene 
Methoden unterschiedlicher Klassen aufnimmt. Allerdings scheitert es 
schon daran, eine Methode einer einzigen Klasse aufzunehmen.
1
  void (GUI::Widget::TextBox::*asdf)() = NULL;
2
  asdf = &GUI::Widget::TextBox::Update;
3
4
  if (asdf != &GUI::Widget::TextBox::Update)
5
    int b = 1;
6
  else
7
    int c = 2;
8
9
        bool d = asdf;

Der Code kompiliert ohne zu meckern, allerdings wird Update() nicht 
angesprungen. Auch stimmen die Adressen nicht überein. Update hat bspws 
0x8004a34 und asdf 0x8004a35. Das ist auch durchgehend so, die Pointer 
haben immer die Adresse der Funktion + 1.

Habe auch schon gegoogelt, aber hat anscheinend nicht viel geholfen.

Hat jemand eine Idee? Ich benutze GCC 5.3.0 + GDB. Programm läuft auf 
einem STM32F4.

von tictactoe (Gast)


Bewertung
2 lesenswert
nicht lesenswert
Reginald L. schrieb:
> bool d = asdf;
1
bool d = irgendein_gui_widget_textbox_ptr->*asdf();
könnte helfen.

Die Sache mit Adresse+1 ist kein Grund zur Sorge. Das ist 
Compiler-interne Angelegenheit.

von tictactoe (Gast)


Bewertung
0 lesenswert
nicht lesenswert
tictactoe schrieb:
> bool d = irgendein_gui_widget_textbox_ptr->*asdf();
> könnte helfen.

Unsinn. Die Funktion hat ja keinen Rückgabewert. Daher nur:
1
irgendein_gui_widget_textbox_ptr->*asdf();

von Reginald L. (Firma: HS Ulm) (reggie)


Bewertung
0 lesenswert
nicht lesenswert
Sry, Copy&Paste Fehler, Rückgabewert ist natürlich bool.

Allerdings will er so auch nicht:
1
  TextBox* consoletb = new TextBox();
2
  consoletb->Create(consolewnd, 0, 0, TFT_PHYSICAL_WIDTH, TFT_PHYSICAL_HEIGHT,
3
    Color::Green, Color::WhiteSmoke, Memory::Resource::Object::GetFont((char*)"MS Sans Serif 12pt"));
4
  consoletb->Show();
5
6
  bool (GUI::Widget::TextBox::*asdf)() = NULL;
7
  asdf = &GUI::Widget::TextBox::Update;
8
  bool b = consoletb->*asdf();

 error : must use '.*' or '->*' to call pointer-to-member function in 
'asdf (...)', e.g. '(... ->* asdf) (...)'
1>    bool b = consoletb->*asdf();

Die Meldung verstehe ich allerdings nicht.

EDIT: Du bist ein Schatz, da muss eine Klammer hin :>
  bool b = (consoletb->*asdf)();

: Bearbeitet durch User
von Reginald L. (Firma: HS Ulm) (reggie)


Bewertung
0 lesenswert
nicht lesenswert
Achso, es geht noch weiter :)

Erst mal danke, da saß ich gestern ein paar Stunden dran, du hast es mit 
deinem 3 Zeiler geschafft mein Problem in einer Minute zu lösen :>

Ist es möglich auch Methoden verschiedener Klassen in einen Pointer zu 
packen? So etwas in Richtung void? Warscheinlich eher weniger, soweit 
ich mich informieren konnte?

von Rolf M. (rmagnus)


Bewertung
0 lesenswert
nicht lesenswert
Reginald L. schrieb:
> Ist es möglich auch Methoden verschiedener Klassen in einen Pointer zu
> packen?

Ja, wenn die Klassen von einer gemeinsamen Basisklasse abgeleitet sind. 
Oder du musst mit Templates arbeiten. Kommt halt drauf an, wofür das 
ganze gut sein soll. Willst du so eine Art Nachbau eines 
signal/slot-Mechanismus machen?

> So etwas in Richtung void? Warscheinlich eher weniger, soweit ich mich
> informieren konnte?

Dann würde ja die ganze Typsicherheit flöten gehen.

von Mark B. (markbrandis)


Bewertung
0 lesenswert
nicht lesenswert
Reginald L. schrieb:
> ich versuche ein Pointer-Array zu erstellen, welche verschiedene
> Methoden unterschiedlicher Klassen aufnimmt.

Darf man fragen, was der Sinn der Sache ist?

von Niklas G. (erlkoenig) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Reginald L. schrieb:
> Ist es möglich auch Methoden verschiedener Klassen in einen Pointer zu
> packen?
Ja, das geht in gewissen Grenzen, indem man std::bind oder Lambdas 
nutzt. Hierbei muss man allerdings bei der Zuweisung direkt die Instanz 
mitgeben. Das Ergebnis ist sozusagen ein polymorpher Funktionszeiger:
1
#include <iostream>
2
#include <functional>
3
4
class A {
5
  public:
6
    void foo (int x) { std::cout << "A::foo(" << x << ")\n"; }
7
};
8
9
class B {
10
  public:
11
    void foo (int x) { std::cout << "B::foo(" << x << ")\n"; }
12
};
13
14
// Variante 1: std::bind
15
void test1 () {
16
  // Instanzen der Klassen
17
  A a;
18
  B b;
19
  
20
  // "Polymorpher" Funktionspointer
21
  std::function<void(int)> fun;
22
  
23
  // Weise A::foo zu, mit Instanz a
24
  fun = std::bind (&A::foo, &a, std::placeholders::_1);
25
  
26
  // Rufe auf
27
  fun (42);
28
  
29
  // Weise B::foo zu, mit Instanz b
30
  fun = std::bind (&B::foo, &b, std::placeholders::_1);
31
  
32
  // Rufe auf
33
  fun (84);
34
}
35
36
// Variante 2: Lambda
37
void test2 () {
38
  // Instanzen der Klassen
39
  A a;
40
  B b;
41
  
42
  // "Polymorpher" Funktionspointer
43
  std::function<void(int)> fun;
44
  
45
  // Weise A::foo zu, mit Instanz a
46
  fun = [&a] (int x) { a.foo (x); };
47
  
48
  // Rufe auf
49
  fun (42);
50
  
51
  // Weise B::foo zu, mit Instanz b
52
  fun = [&b] (int x) { b.foo (x); };
53
  
54
  // Rufe auf
55
  fun (84);
56
}
57
58
int main() {
59
  test1 ();
60
  test2 ();
61
  return 0;
62
}

Die Ausgabe ist:
1
A::foo(42)
2
B::foo(84)
3
A::foo(42)
4
B::foo(84)

Siehe:
http://ideone.com/3DAA7L
http://en.cppreference.com/w/cpp/utility/functional/bind
http://en.cppreference.com/w/cpp/utility/functional/function

: Bearbeitet durch User
von Tom (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>Ist es möglich auch Methoden verschiedener Klassen in einen Pointer zu
packen?

Das geht, wird aber beliebig hässlich und wackelig.

Wenn man die Klassen, auf deren Methoden man zugreifen will, nicht 
selbst schreibt und so keine Interfaces einführen kann, kann man mit 
std::function und std::bind etwas bauen:
1
#include <iostream>
2
#include <functional>
3
#include <vector>
4
5
class Foo
6
{
7
public:
8
    Foo(const std::string& s) : s_(s)
9
    {
10
    }
11
12
    void doit() const
13
    {
14
        std::cout << "Foo(" << s_ << ")\n";
15
    }
16
17
    int bla()
18
    {
19
        std::cout << "Foo::bla()\n";
20
        return 23;
21
    }
22
23
private:
24
    std::string  s_;
25
};
26
27
28
class Bar
29
{
30
public:
31
    Bar() : i_(23)
32
    {
33
    }
34
35
    void prnt() const
36
    {
37
        std::cout << "Bar(" << i_ << ")\n";
38
    }
39
40
    void set(int i)
41
    {
42
        i_ = i;   
43
    }
44
45
private:
46
    int i_;
47
};
48
49
50
51
int main()
52
{
53
    typedef std::function<void(void)> VoidVoidFunc; 
54
    
55
 
56
    Foo foo1{"foo1"};
57
    Foo foo2{"foo2"};
58
    Bar bar;
59
60
    std::vector<VoidVoidFunc> functions;
61
62
    functions.push_back( std::bind(&Foo::doit, &foo1) );
63
    functions.push_back( std::bind(&Foo::doit, &foo2) );
64
    functions.push_back( std::bind(&Bar::prnt, &bar) );
65
    functions.push_back( std::bind(&Bar::set, &bar, 1111) );
66
    functions.push_back( std::bind(&Bar::prnt, &bar) );
67
    functions.push_back( std::bind(&Foo::bla, &foo2) );
68
69
    for (auto& f: functions)
70
    {
71
        f();
72
    }
73
}


Nachtrag: Zu langsam getippt ;)

von Reginald L. (Firma: HS Ulm) (reggie)


Bewertung
0 lesenswert
nicht lesenswert
Hui! Vielen lieben Dank dafür! Ich fang gleich mal an mich 
reinzuarbeiten!

von Tom (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
Die Lambdas sind etwas netter als std::bind, weil damit auch solche 
Schweinereien ganz einfach funktionieren:
1
  functions.push_back( [&foo1, &bar]() {int tmp = foo1.bla();  bar.set(tmp);} );
Bei all diesen Features muss man sehr darauf achten, sie nicht 
übertrieben einzusetzen. Die Möglichkeiten sind verlockend, aber man 
kann sehr leicht völlig undurchdringliche Programmstrukturen bauen. 
std::function ist auch nichts für extrem performancekritische 
Anwendungen.

von Reginald L. (Firma: HS Ulm) (reggie)


Bewertung
0 lesenswert
nicht lesenswert
Schweinereien find ich toll :D.
Die Performance ist mir lediglich beim Zugriff auf die Memberfunktion 
wichtig, da, bei Bedarf, in der Mainloop darauf zugegriffen wird. Die 
Initialisierungen finden beim Programmstart statt.

Von std::function habe ich auch schon gehört, heute wirds mir aber schon 
zu spät, morgen ist auch noch ein Tag.

: Bearbeitet durch User
von Reginald L. (Firma: HS Ulm) (reggie)


Bewertung
0 lesenswert
nicht lesenswert
So, ich habe mich jetzt ein wenig in std:function eingelesen,
Tom schrieb:
> std::function ist auch nichts für extrem performancekritische
> Anwendungen.
und das gefällt mir nicht so ganz. Gibt es noch andere Möglichkeiten? Es 
geht lediglich um statische Methoden einer Klasse die in die Interrupts 
eingebunden werden sollen. So eine Art lookuptable die der Interrupt 
durchrennt.

von Niklas G. (erlkoenig) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Reginald L. schrieb:
> Es
> geht lediglich um statische Methoden einer Klasse die in die Interrupts
> eingebunden werden sollen.
Ach statische Funktionen! Das macht die Sache viel einfacher. Da 
kannst du ordinäre Funktions-Pointer nutzen, denn statische 
Member-Funktionen verhalten sich effektiv wie freie Funktionen:
1
#include <iostream>
2
3
class A {
4
  public:
5
    static void foo (int x) { std::cout << "A::foo(" << x << ")\n"; }
6
};
7
8
class B {
9
  public:
10
    static void foo (int x) { std::cout << "B::foo(" << x << ")\n"; }
11
};
12
13
// Definiere Funktionspointer-Typ
14
using fun_t = void (*) (int);
15
16
int main () {
17
  fun_t f = &A::foo;
18
  f (42);
19
  f = &B::foo;
20
  f (33);
21
}

von Reginald L. (Firma: HS Ulm) (reggie)


Bewertung
0 lesenswert
nicht lesenswert
Gerade habe ich einfach mal so folgendes gemacht und es scheint zu 
funktionieren:
1
class IRQ
2
{
3
public:
4
        init(void(*irq_handler_func)(void))
5
{
6
 exti_handler[0] =  irq_handler_func;
7
};
8
private:
9
  static void (*exti_handler[5])(void);
10
11
  friend void ::EXTI0_IRQHandler();
12
};
13
14
extern "C" void EXTI9_5_IRQHandler()
15
{
16
  Stm32::IRQ::exti_handler[0]();
17
  asm("bkpt 255");
18
}
19
20
inline void Stm32::Ethernet::temp()
21
{
22
  int x = 1;
23
}
Allerdings verstehe ich von der Materie zu wenig, als dass ich sagen 
könnte ob die Methode ansatzweise elegant ist.

von Reginald L. (Firma: HS Ulm) (reggie)


Bewertung
0 lesenswert
nicht lesenswert
Niklas G. schrieb:
> Das macht die Sache viel einfacher. Da
> kannst du ordinäre Funktions-Pointer nutzen, denn statische
> Member-Funktionen verhalten sich effektiv wie freie Funktionen:
Hui ging ja fix! Gerade erst gelesen.
Das mit dem Funktionspointer-Typ finde ich zumindest schonmal eleganter, 
danke erstmal, ich mach mich ran :)

Niklas G. schrieb:
> Ach statische Funktionen!
Ja, ich stehe gerade vor einer anderen Aufgabe, dachte nur, es wäre 
unsinnig einen neuen Thread zu eröffnen, da das hier ganz gut reinpasst. 
Das Problem mit der GUI Klasse ist geblieben, aber momentan noch auf 
Eis.

Danke nochmal!

von Niklas G. (erlkoenig) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Reginald L. schrieb:
> init(void(*irq_handler_func)(void))
Fehlt da nicht der Rückgabe-Typ...

Reginald L. schrieb:
> Das mit dem Funktionspointer-Typ finde ich zumindest schonmal eleganter,
Ja das erhöht die Lesbarkeit enorm...

Reginald L. schrieb:
> als dass ich sagen
> könnte ob die Methode ansatzweise elegant ist.
So ist die Anzahl Callbacks halt auf 5 beschränkt... Etwas schlauer wäre 
es, eine intrusive verkettete Liste zu machen. Der Benutzer legt dann 
pro Callback-Funktion eine globale Instanz von Observer an:
1
#include <iostream>
2
3
// Funktionszeiger-Typ definieren für Callback-Funktion
4
using IrqHandlerFun = void (*) ();
5
6
class Publisher;
7
8
// Element der verketteten Liste (intrusive List)
9
class Observer {
10
  public:
11
    constexpr inline Observer (IrqHandlerFun f) : function (f), next (nullptr) {}
12
    // Alternative: Direkt registrieren
13
    Observer (IrqHandlerFun f, Publisher& pub);
14
    // Funktionszeiger
15
    IrqHandlerFun function;
16
    // Zeiger auf nächstes Element
17
    Observer* next ;
18
};
19
20
// Für jeden Interrupt wird ein Publisher angelegt
21
class Publisher {
22
  public:
23
    // Observer hinzufügen und verketten
24
    void addObserver (Observer* o) {
25
      if (first)
26
        o->next = first;
27
      else
28
        o->next = nullptr;
29
      first = o;
30
    }
31
    // Alle Funktionszeiger aufrufen
32
    void call () {
33
      for (Observer* scan = first; scan; scan = scan->next) {
34
        scan->function ();
35
      }
36
    }
37
  private:
38
    Observer* first = nullptr;
39
};
40
41
Observer::Observer (IrqHandlerFun f, Publisher& pub) : function (f), next (nullptr) {
42
  pub.addObserver (this);
43
}
44
45
46
// Publisher für IRQ's anlegen
47
48
Publisher pubIRQ_EXTI9_5;
49
50
extern "C" void EXTI9_5_IRQHandler() {
51
  // Alle Funktionszeiger aufrufen
52
  pubIRQ_EXTI9_5.call ();
53
}
54
55
Publisher pubIRQ_TIM3;
56
57
extern "C" void TIM3_IRQHandler() {
58
  // Alle Funktionszeiger aufrufen
59
  pubIRQ_TIM3.call ();
60
}
61
62
63
// === User Code ===
64
65
66
// Eine statische Member-Funktion
67
class MyHandler1 {
68
  public:
69
    static void handlerFun () {
70
      std::cout << "MyHandler1::handlerFun\n";
71
    }
72
};
73
Observer MyHandler1_Observer { &MyHandler1::handlerFun };
74
75
76
// Eine freie Funktion
77
void OtherHandlerFun () {
78
  std::cout << "OtherHandlerFun\n";
79
}
80
81
// Automatische Registration nutzen
82
Observer OtherHandler_Observer { &OtherHandlerFun, pubIRQ_EXTI9_5 };
83
84
85
int main () {
86
  pubIRQ_EXTI9_5.addObserver (&MyHandler1_Observer);
87
  
88
  // Simuliere Interrupts
89
  EXTI9_5_IRQHandler ();
90
  TIM3_IRQHandler ();
91
}

Noch eleganter ist dann das komplette Observer-Pattern:
1
#include <iostream>
2
3
class Publisher;
4
5
// Abstrakte Basisklasse für Handler-Klassen
6
class Observer {
7
  friend class Publisher;
8
  public:
9
    virtual void event () = 0;
10
  private:
11
    Observer* next;
12
};
13
14
class Publisher {
15
  public:
16
    // Observer hinzufügen und verketten
17
    void addObserver (Observer* o) {
18
      if (first)
19
        o->next = first;
20
      else
21
        o->next = nullptr;
22
      first = o;
23
    }
24
    // Alle Funktionszeiger aufrufen
25
    void call () {
26
      for (Observer* scan = first; scan; scan = scan->next) {
27
        scan->event ();
28
      }
29
    }
30
  private:
31
    Observer* first = nullptr;
32
};
33
34
// Publisher für IRQ's anlegen
35
36
Publisher pubIRQ_EXTI9_5;
37
38
extern "C" void EXTI9_5_IRQHandler() {
39
  // Alle Funktionszeiger aufrufen
40
  pubIRQ_EXTI9_5.call ();
41
}
42
43
Publisher pubIRQ_TIM3;
44
45
extern "C" void TIM3_IRQHandler() {
46
  // Alle Funktionszeiger aufrufen
47
  pubIRQ_TIM3.call ();
48
}
49
50
// === User Code ===
51
52
class Handler1 : public Observer {
53
  public:
54
    virtual void event () {
55
      std::cout << "Handler1::event\n";
56
    }
57
} handler1;
58
59
class Handler2 : public Observer {
60
  public:
61
    virtual void event () {
62
      std::cout << "Handler2::event\n";
63
    }
64
} handler2;
65
66
int main () {
67
  pubIRQ_EXTI9_5.addObserver (&handler1);
68
  pubIRQ_TIM3.addObserver (&handler2);
69
70
  pubIRQ_EXTI9_5.call ();
71
  pubIRQ_TIM3.call ();
72
}
So muss man gar nicht mehr mit Funktionspointern herumhantieren sondern 
nutzt das wohlbekannte Observer Pattern. Einzige Modifikation ist hier 
der "next" Pointer, um eine Liste aufzubauen.

Implementierung von Funktionen zum Entfernen von Observern sind dem 
Leser überlassen ;-)

von Reginald L. (Firma: HS Ulm) (reggie)


Bewertung
0 lesenswert
nicht lesenswert
Niklas G. schrieb:
> Fehlt da nicht der Rückgabe-Typ...
Ich war schlampig beim umtippen. Entschuldigung :P

Na da hasste mir ne Steilvorlage geliefert, da muss ich ja gar nicht 
mehr nachdenken ;)

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]
  • [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.