Forum: Mikrocontroller und Digitale Elektronik c++ Funktionspointer


von Nils O. (deeznutz)


Lesenswert?

Tag Leute,

ich habe gerade ein Paar Probleme mit der Verwendung von 
Methodenpointern.

Ich verwende ein Arduino Board unter Atmel Studio 6, habe die Arduino 
lib eingebunden, und ein Programm zum kommunizieren mit einer Pumpe 
geschrieben. Verwendet werden timer-Funktionen, die einen 
Funktionszeiger übergeben bekommen:
1
Timer t;
2
3
// Prototyp timer-funktion
4
// int Timer::after(long period, void (*callback)())
5
6
void sendCommand(){...}
7
8
void connect(){
9
  t.after(1000, sendCommand);
10
}
11
12
void setup() {
13
  connect();
14
}

Passt soweit auch alles. Jetzt möchte ich die Pumpenspezifischen 
funktionen in eine Klasse auslagern und als Modul hinzufügen.

Pumpe.h:
1
class Pumpe
2
{
3
  public:
4
    Pumpe();
5
    void connect();
6
    void sendCommand();
7
};

Pumpe.cpp:
1
#include "Pumpe.h"
2
#include "Timer.h"
3
4
Timer t;
5
6
void Pumpe::sendCommand(){...}
7
8
void Pumpe::connect()
9
{
10
  t.after(1000, sendCommand);
11
}

Compilieren schmeisst den fehler: "Fehler no matching function for call 
to 'Timer::after(int&, <unresolved overloaded function type>)"

Kann mir bitte mal jemand erklären, wie ich in diesem Fall auf die 
Funktion zeige?

Danke!

Gruß

Nils

von Klaus W. (mfgkw)


Lesenswert?

Und wie sieht Timer aus?

Wenn dort (wie im auskommentierten Teil angedeutet) kene Pumpe::-Methode 
als zweiter Parameter von after() steht, wirst du dort auch keine 
(nichtstatische) Pumpe::-Methode angeben können, weil die braucht für 
ihren Aufruf ja ein Pumpe-Objekt, was sie aber wahrscheinlich gar nicht 
hat.

Evtl. willst du sendCommand() static machen? Dann könnte es gehen; ich 
weiß aber nicht, was du genau vorhast.

von Nils O. (deeznutz)


Lesenswert?

Hi,

ich hätte vermutet, dass es irgendwie a lá
1
t.after(1000, this->sendCommand);
 funktioniert!?

Wie sähe das mit einer statischen Methode aus? Sorry, C++ ist ein paar 
jährchen her...

von Klaus W. (mfgkw)


Lesenswert?

Nils O. schrieb:
> ich hätte vermutet, dass es irgendwie a lát.after(1000, this->sendCommand); 
funktioniert!?

Das this hier sagt doch nur, welches sendCommand() übergeben werden soll 
(als Funktionszeiger).
Ein zugehöriges Objekt, um über diesen Funktionszeiger dann eine Methode 
(Pumpe::sendcommand()) aufzurufen, hat dein Timer doch davon immer noch 
nicht.
Braucht dein sendCommand() nun ein Pumpe-Objekt oder nicht?

Solange du nicht klar sagen kannst, was du vorhast, kann dir keiner 
helfen, außer mit dem guten Rat, C++ zu lernen.

Wie soll man aus deinen Schnipseln erraten, was genau passieren soll?

von Klaus W. (mfgkw)


Lesenswert?

Nils O. schrieb:
> Wie sähe das mit einer statischen Methode aus? Sorry, C++ ist ein paar
> jährchen her...

Das sieht so aus, daß die MEthode als static deklariert wird und dann 
ohne Objekt laufen kann.
Dafür darf sie natürlich auf nichts zugreifen in der Klasse, was nicht 
ebenfalls static ist (Methoden, Elemente).

von Trölf-Mionnnnnen (Gast)


Lesenswert?

http://www.functionx.com/cpp/Lesson21.htm -> Static Methods

&(pObj->sendcommand()) sollte auch funzen (req.: es gibt eine Instanz 
von Pumpe) oder irre ich?

von Klaus W. (mfgkw)


Lesenswert?

Trölf-Mionnnnnen schrieb:
> &(pObj->sendcommand()) sollte auch funzen (req.: es gibt eine Instanz
> von Pumpe) oder irre ich?

Das mag sein, aber der Timer muß das Objekt auch kennen, um eine 
nichtstatische Methode aufzurufen.

von Klaus W. (mfgkw)


Lesenswert?

Trölf-Mionnnnnen schrieb:
> &(pObj->sendcommand())

äh, was sol eigentlich das & davor?
Redest du von der Übergabe beim Aufrufer, oder von der Verwendung in 
Timer?

von Karl H. (kbuchegg)


Lesenswert?

1
Timer t;
2
3
// Prototyp timer-funktion
4
// int Timer::after(long period, void (*callback)())
5
6
7
void Pumpe::connect()
8
{
9
  t.after(1000, sendCommand);
10
}

Das geht so nicht.
Die after Methode des Timer Objekts kann nur eine Funktion aufrufen, die 
die Signatur einer void Funktion hat. Deine sendCommand Funktion 
qualifiziert sich aber nicht dafür, denn der Klassenname ist hier immer 
Teil der Funktionssignatur.
Das Problem an dieser Stelle ist, dass du nicht einfach die sendCommand 
Funktion aufrufen kannst, sondern immer auch ein entsprechendes Pumpe 
Objekt brauchst, für welches diese Funktion aufgerufen werden soll. Bei 
derartigen Funktionsaufrufen sind daher immer 2 'Informationen' 
notwendig:
a) für welches Objekt soll
b) welche Funktion aufgerufen werden.

Wenn du dir aber
// int Timer::after(long period, void (*callback)())
ansiehst, dann ist auch klar, dass das Timer Objekt nichts von einem 
Pumpe Objekt wissen kann! Daher kann es auch keine Member-Funktionen 
(ausser statischen, aber die helfen dir nichts) eines Pumpe Objekts 
aufrufen, ganz einfach deswegen, weil das Objekt selber in dieser ganzen 
Schnittstelle nirgends auftaucht. Und Funktionspointer in C++ beinhalten 
nicht die Information über ein Objekt, sondern nur die Information über 
die Methode einer Klasse. Das Objekt geht extra.

Was du aber tun kannst, ist ein Umweg. Du kannst eine standalone 
Funktion machen, die über das Objekt Bescheid weiß, und im Auftrag des 
Timers dann die entsprechende Funktion für DIESES Objekt aufruft.
1
Timer t;
2
3
// Prototyp timer-funktion
4
// int Timer::after(long period, void (*callback)())
5
6
7
void Pumpe::sendCommand(){...}
8
9
void Pumpe::connect()
10
{
11
  t.after(1000, sendCommandHelper);
12
}
13
14
Pumpe thePumpe;   // die einzig wahre Pumpe
15
                  // hier als globales Objekt ausgeführt
16
17
void sendCommandHelper(){
18
  thePumpe.sendCommand();
19
}

Vom Timer wird die standalone Funktion 'sendCommandHelper' aufgerufen 
und diese Funktion ruft dann ihrerseits wieder die 'sendCommand' Methode 
einer bestimmten Pumpe (über das bekannte Objekt) auf.

von Nils O. (deeznutz)


Angehängte Dateien:

Lesenswert?

Dem Timer soll es eigentlich egal sein von welchem Modul eine 
"Timeranfrage" kommt. Ich habe mal das Programm angehängt.

Wichtig ist nur die PilotC::connect-Methode, die eine zyklischen Timer 
starten soll, der wiederum eine andere Methode der PilotC-Klasse 
aufruft.

Also müsste ich die Timer Klasse dahingehend ändern, dass sie keinen 
"void (*callback)()" Pointer in C Manier entgegennimmt sondern das C++ 
Objekt samt Methode?

von Nils O. (deeznutz)


Lesenswert?

Ups, hatte den Text noch nicht gelesen...

von Karl H. (kbuchegg)


Lesenswert?

Nils O. schrieb:

> Also müsste ich die Timer Klasse dahingehend ändern, dass sie keinen
> "void (*callback)()" Pointer in C Manier entgegennimmt sondern das C++
> Objekt samt Methode?


Wenn du das kannst: Ja das wäre eine Möglichkeit.

C++ Leute würden das unter Umständen auch anders machen.
Sie würden zum Timer eine zusätzliche Klasse definieren, welche ein 
TimerCallback Objekt beschreibt, welches über eine virtuelle Funktion 
Callback Funktion verfügt. Der Timer bekommt einen Pointer auf so ein 
TimerCallback Objekt und wenn die Zeit um ist, dann ruft er die Callback 
Funktion auf.

Die Pumpe wiederrum leitet sich von so einer TimerCallback Klasse ab und 
ist damit potentieller Kandidat für etwas, was eienen Rückruf vom Timer 
erhalten kann. Noch schnell die Callback Methode in Pumpe implementiert 
und fertig ist die ganze Schose.

In C++ braucht man ganz selten Funktionspointer. Fast immer gibt es 
bessere Möglichkeiten.

von Karl H. (kbuchegg)


Lesenswert?

Ok. (Grade ins Zip-File geschaut.

Bei dir ist es dann eben ein Event-Callback Objekt, welches 
Ansprechpartner für einen Event ist.
1
class EventCallbackObject
2
{
3
  public:
4
    virtual ~EventCallbackObject() {}
5
    virtual void callback() = 0;
6
};
7
8
class Event
9
{
10
public:
11
  Event(); 
12
  int update();
13
 
14
  int  eventType;
15
  long period;
16
  int  repeatCount;
17
  int  pin;
18
  int  pinState;
19
  EventCallbackObject* theObject;
20
  long lastEventTime;
21
  int  count;
22
};
23
24
25
26
....
27
28
29
int Event::update()
30
{
31
  unsigned long now = millis();
32
  if (now > lastEventTime + period)
33
  {
34
    switch (eventType)
35
    {
36
      case EVENT_EVERY:
37
        if (theObject)
38
          theObject->callback();
39
        break;
40
41
42
....
und eine Pumpe ist dann eben so ein EventCallbackObject Object, welche 
eine entsprechende Methode implementiert
1
class Pumpe : public EventCallbackObject
2
{
3
  ....
4
5
  virtual void callback();
6
7
  ....
8
};
9
10
11
void Pumpe::callback()
12
{
13
  ....
14
}
15
16
17
void Pumpe::connect()
18
{
19
  t.after(1000, this);  // nach 1000 Zeiteinheiten bitte die Methode
20
                        // callback vom 'this' Objekt aufrufen
21
}


voila. Keine Funktionspointer. Polymorphie erledigt die Magie.

von Nils O. (deeznutz)


Lesenswert?

Hi,

vielen Dank! Obwohl es sehr verständlich ist, muss ich mir das gleich 
mal in Ruhe zu Gemüte führen...

Gruß

Nils

von Nils O. (deeznutz)


Lesenswert?

Hi,

leider ist so nur ein Callback pro Klasse möglich. Wie kann ich denn dem 
Timer eine beliebige Methode der Klasse zusätzlich zur Instanz 
derselbigen übergeben?

von Karl H. (kbuchegg)


Lesenswert?

Nils O. schrieb:
> Hi,
>
> leider ist so nur ein Callback pro Klasse möglich. Wie kann ich denn dem
> Timer eine beliebige Methode der Klasse zusätzlich zur Instanz
> derselbigen übergeben?

Welche Version willst du?
Die schmutzige oder die saubere nach den Regeln der Kunst?

Alte C++ Weisheit:
Wenn du ein Problem hast, verpack es in eine Klasse.

zb. Kombination mit Funktionspointern
1
class PumpeProxy : public EventCallbackObject
2
{
3
  public:
4
    PumpeProxy( Pumpe& pump, void (Pumpe::*method)() ):
5
      pump_( pump ), method_( method ) {}
6
    virtual void callback() { (pump_.*method)(); }
7
8
  private:
9
    Pumpe& pump_;
10
    void (Pumpe::*method_)();
11
}

Jetzt hast du eine Klasse, die ein Pumpenobjekt mit einem 
Funktionszeiger verknüpft und da das ganze von EventCallbackObject 
abgeleitet ist, kann man so ein Objekt in den Timer stopfen. Wenn der 
Timer die callback() Methode aufruft, dann leitet dieses Objekt diesen 
Aufruf an die angegebenen Methode im Pumpen Objekt weiter.

Die 'schmutzige' ist weit nicht so interessant.
Bei der 'Anmeldung' beim Timer gibst du neben dem Objekt noch eine Id 
mit (einfach eine Zahl). Diese Id wird mit dem Event mitgespeichert und 
wenn der Callback aus dem Event erfolgt, wird die Id wieder mitgegeben. 
In der callback Funktion kann man dann mithilfe der Id aussortieren, 
welcher Callback das ist.

von Nils O. (deeznutz)


Lesenswert?

Man, man, man, irgendwie scheint mir die c++ Geschichte für solch 
einfache Aufgaben zu kompliziert. Ich glaub ich bleib für meinen 
embedded Kram lieber bei C. Das kann ich wenigstens einigermaßen :-)

Ich werd's trotzdem morgen mal mit Deiner Variante probieren. Vielen 
Dank!

Gruß und einen schönen Abend noch!

Nils

von Karl H. (kbuchegg)


Lesenswert?

Nils O. schrieb:
> Man, man, man, irgendwie scheint mir die c++ Geschichte für solch
> einfache Aufgaben zu kompliziert.

:-)

Du merkst gerade, dass objektorientiertes Programmieren andere 
Denkweisen erfordert. Und ein paar Dinge sind dann in C++ 'unelegant' 
gelöst.

von Tüdelütütü (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Und ein paar Dinge sind dann in C++ 'unelegant'
> gelöst.

... können ... 'unelegant' gelöst werden.

von Klaus W. (mfgkw)


Lesenswert?

... werden es auch oft ...

Vor allem, wenn nicht klar ist, was man eigentlich will.

von Karl H. (kbuchegg)


Lesenswert?

Klaus Wachtler schrieb:
> ... werden es auch oft ...

Na ja.
Die C# Delegates sind schon deutlich eleganter als C++ 
Member-Funktionspointer.
Muss man neidlos zugeben.

von Klaus W. (mfgkw)


Lesenswert?

Das Hauptproblem bei Funktions-/Methodenzeigern ist eine etwas krude 
Syntax und daß sie halt keiner mag.
Wenn man dann aber bereit ist, auf Funktionsobjekte zu wechseln (wie von 
KHB oben gezeigt), ist es weder schwer zu verstehen noch schlimm zu 
schreiben.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Die von Karl Heinz vorgestellte Methode kann noch verallgemeinert
werden, indem man die Klasse des Objekts, dessen Methode aufgerufen
werden soll, als Templateparameter übergibt:

callback.h:
1
// Allgemeine Callback-Basisklasse, über die eine beliebige argumentlose
2
// Methode eines beliebigen Objekts aufgerufen werden kann
3
4
class CallbackBase {
5
  public:
6
    virtual void operator()() = 0;
7
};
8
9
template<class C>
10
class Callback : public CallbackBase {
11
  public:
12
    Callback(C &object, void (C::*method)()):
13
      object_(object), method_(method) {}
14
    virtual void operator()() { (object_.*method_)(); }
15
16
  private:
17
    C &object_;
18
    void (C::*method_)();
19
};


Anwendungsbeispiel:
1
#include <iostream>
2
#include "callback.h"
3
4
using namespace std;
5
6
// Event kennt nur die allgemeine Basisklasse CallbackBase und ist deswegen
7
// ebenfalls allgemein einsetzbar
8
class Event {
9
  public:
10
    void setCallback(CallbackBase &cb) { callback_ = &cb; }
11
    void update() {
12
      /*...*/
13
      (*callback_)();
14
      /*...*/
15
    }
16
17
  private:
18
    CallbackBase *callback_;
19
};
20
21
// Klasse, deren Methode als Callback aufgerufen werden soll
22
class ExampleClass {
23
  public:
24
    void show() { cout << "Object of class ExampleClass" << endl; }
25
};
26
27
int main() {
28
  Event event;
29
  ExampleClass exampleObject;
30
31
  // Anlegen eines Callback-Objekts für die Methode show des Objekts
32
  // exampleObject
33
  Callback<ExampleClass> exampleCallback(exampleObject, &ExampleClass::show);
34
  //       —— Klasse ——                  —— Objekt ———  ————— Methode —————
35
36
  // Bekanntmachen des Callback-Objekts beim Event
37
  event.setCallback(exampleCallback);
38
39
  // .... und Action
40
  event.update();
41
42
  return 0;
43
}


Nils O. schrieb:
> ich hätte vermutet, dass es irgendwie a lá
>  t.after(1000, this->sendCommand);
> funktioniert!?

Ja, das wäre der intuitive, logische und elegante Weg, wie er bspw. in
Python problemlos möglich ist. Leider ist er in C++ nicht vorgesehen,
weswegen man Verrenkungen der obigen Art machen muss, um vergleichbares
Verhalten zu erzielen.

Da Ausdrücke der Art
1
  objekt.methodenname

und
1
  objektzeiger->methodenname

semantisch nicht belegt sind (der Compiler meldet sie als Fehler), wäre
es theoretisch denkbar, sie in einem zukünftigen C++-Standard als
Funktionszeiger mit gebundenem Objektargument zu definieren, der wie
jeder andere Funktionszeiger verwendet werden kann.

Im aktuellen C++-Standard (C++11) ist dies jedenfalls noch nicht
möglich, dafür gibt es als neue, sehr mächtige Konstrukte Lambdas und
Closures, mit denen man u.a. auch komplexe Callback-Mechanismen ohne
selbstgeschriebene Wrapper-Klassen realisieren kann. Das obige Beispiel
würde in C++11 so aussehen:
1
#include <iostream>
2
#include <functional>
3
4
using namespace std;
5
6
class Event {
7
  public:
8
    void setCallback(function<void ()> &cb) { callback_ = &cb; }
9
    void update() {
10
      /*...*/
11
      (*callback_)();
12
      /*...*/
13
    }
14
15
  private:
16
    function<void ()> *callback_;
17
};
18
19
// Klasse, deren Methode als Callback aufgerufen werden soll
20
class ExampleClass {
21
  public:
22
    void show() { cout << "Object of class ExampleClass" << endl; }
23
};
24
25
int main() {
26
  Event event;
27
  ExampleClass exampleObject;
28
29
  // Anlegen eines Closures für den Aufruf der Methode show des Objekts
30
  // exampleObject
31
  function<void ()> exampleCallback = [&] () { exampleObject.show(); };
32
33
  // Bekanntmachen des Closures beim Event
34
  event.setCallback(exampleCallback);
35
36
  // .... und Action
37
  event.update();
38
39
  return 0;
40
}

So richtig schön sieht das immer noch nicht aus, aber man kommt immerhin
ohne die Callback-Klasse aus dem obigen Beispiel aus und könnte bspw.
wie in folgendem Beispiel der Callback-Funktion eine Belegung der
Funktionsargumente mitgeben:
1
  int x = 200, y = 50;
2
  function<void ()> exampleCallback = [&] () { exampleObject.showXY(x, y); };

An der Event-Klasse muss hierzu nichts geändert werden.

Edit: Da war noch ein Fehler drin. Das function<>-Objekt darf nicht
kopiert, sondern muss als Referenz an setCallback übergeben werden.
Hab's korrigiert.

von netseal (Gast)


Lesenswert?

Hallo,

ich würde eine Basisklasse definieren die sendCommand meinetwegen
virtual implementiert. Davon wird dann alles abgeleitete, was 
sendCommand
können muss.

Mit dem Cast:
t.after((BaseClass*)Pumpe1);
meckert der Compiler nicht mehr.


Grüße
Florian

von Karl H. (kbuchegg)


Lesenswert?

netseal schrieb:
> Hallo,
>
> ich würde eine Basisklasse definieren die sendCommand meinetwegen
> virtual implementiert. Davon wird dann alles abgeleitete, was
> sendCommand
> können muss.
>
> Mit dem Cast:
> t.after((BaseClass*)Pumpe1);
> meckert der Compiler nicht mehr.

Lass den Cast weg.
Generelle Faustregel: Caste so wenig, wie nur irgendwie möglich.

Wenn du Casten MUSST, ist das oft ein Zeichen, dass an einer ganz 
anderen Stelle etwas nicht stimmt. Da die Pumpen-Klasse dann sowieso von 
deiner BaseClass abgeleitet sein muss, ist jedes Pumpen-Objekt damit 
auch automatisch ein BaseClass Objekt und ein Pointer darauf kann an 
t.after einfach so übergeben werden. Das baut sich der Compiler dann 
auch ohne Cast-Unterstützung richtig zurecht. Wenn er da dann meckert, 
dann liegt der Fehler in der Ableitungshierarchie und dann muss die 
korrigiert werden und nicht der Compilre mit einem Cast mundtot gemacht 
werden.

Casts sind Waffen! Und mit genau derselben Umsicht sollten die auch in 
genau derselben Seltenheit eingesetzt werden.

Im übrigen sind wir dann genau bei dieser Lösung
Beitrag "Re: c++ Funktionspointer"

von Nils O. (deeznutz)


Lesenswert?

Vielen Dank für die zahlreichen Hinweise. Ich werde mir jetzt mal was 
passendes zusammenbauen...

Gruß

Nils

von Maximilian (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Generelle Faustregel: Caste so wenig, wie nur irgendwie möglich.

Und wenn, dann keine C-Style-Casts. Die sind nur ganz selten notwendig.

von netseal (Gast)


Lesenswert?

Haha,

ihr seid ja nur neidisch, weil ich meine Lösung
in 4 Zeilen packen konnte.

Grüße
Florian

von Rolf Magnus (Gast)


Lesenswert?

Maximilian schrieb:
> Karl Heinz Buchegger schrieb:
>> Generelle Faustregel: Caste so wenig, wie nur irgendwie möglich.
>
> Und wenn, dann keine C-Style-Casts. Die sind nur ganz selten notwendig.

In Standard-C++ sind sie eigentlich nie notwendig. Das einzige, wozu man 
sie manchmal braucht, ist das Casten zwischen Objekt- und 
Funktionszeiger, und das ist nach Standard-C++ eigentlich gar nicht 
erlaubt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Yalu X. schrieb:

> Ja, das wäre der intuitive, logische und elegante Weg, wie er bspw. in
> Python problemlos möglich ist. Leider ist er in C++ nicht vorgesehen,
> weswegen man Verrenkungen der obigen Art machen muss, um vergleichbares
> Verhalten zu erzielen.

D.h. man muß virtuelle Funktionen (oder schlimmeres) verwenden, was in 
C über einen Funktionszeiger geht?

Damit zieht man sich VTables rein, die GCC bekanntlich im RAM ablegt. 
Für das Beispiel dürften die VTables nicht allzu groß werden, aber 
unangenehm ist das dennoch.  Das Programm ist ja für einen AVR 
(Arduino).

> Im aktuellen C++-Standard (C++11) ist dies jedenfalls noch nicht
> möglich, dafür gibt es als neue, sehr mächtige Konstrukte Lambdas und
> Closures, mit denen man u.a. auch komplexe Callback-Mechanismen ohne
> selbstgeschriebene Wrapper-Klassen realisieren kann. Das obige Beispiel
> würde in C++11 so aussehen:
>
1
#include <functional>
2
3
using namespace std;
4
5
class Event {
6
  public:
7
    void setCallback(function<void ()> cb) { callback_ = &cb; }
8
    void update() {
9
      /*...*/
10
      (*callback_)();
11
      /*...*/
12
    }
13
14
  private:
15
    function<void ()> *callback_;
16
};

Nehmen wir mal an, es gäbe ein <functional> im avr-g++. <functionl> 
gibt's zumindest bei avr-g++ 4.8 noch nicht (getestet mit avr-g++ 4.8 
vom 2012-09-15).

Wie würde der erzeugte Code aussehen? Braucht das auch VTables?

Oder sogar noch schlimmeres wie Trampolins (Entspricht den lokalen 
Funktionen von GNU-C), die auf AVR schon überhaupt nicht implementierbar 
sind — noch nicht mal ineffizient — weil es bei AVRs nicht möglich ist, 
Code auf dem Stack auszuführen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Johann L. schrieb:
> D.h. man muß virtuelle Funktionen (oder schlimmeres) verwenden, was in
> C über einen Funktionszeiger geht?

Nein, freie Funktionen und Klassenmethoden (also diejenigen, die in der
Klassendeklaration mit "static" deklariert sind) können als gewöhnliche
Funktionszeiger übergeben werden wie in C auch. Da gibt es keinerlei
Nachteile.

Soll aber eine Methode eines Objekts als Callback aufgerufen werden,
braucht der Aufrufer nicht nur den Methodenzeiger, sondern zusätzlich
noch einen Zeiger auf das Objekt, was die Sache etwas komplizierter
macht.

Wenn das Objekt immer von der gleichen (Basis-)Klasse und diese Klasse
dem Aufrufer bekannt ist, geht das ohne virtuelle Methoden. Der Metho-
denaufruf dauert natürlich etwas länger als ein Funktionsaufruf, weil
zusätzlich der Objektzeiger übergeben werden muss.

Auch unterschiedlichen Klassen lassen sich durch Umcasten von Methoden
und Objektzeigern ohne weiteren Effizienzverlust verarbeiten. Dann geht
allerdings die Typsicherheit verloren, was gerade bei der Programmierung
in callback-lastigen Frameworks ein großer Nachteil ist.

Um das Ganze typsicher zu machen, kommt man in C++-98 an virtuellen
Methoden nicht vorbei.

Aber so schlimm sind die ja auch wieder nicht. Ich habe mal für das
Beispiel von oben in vier Varianten die Laufzeiten für den Aufruf einer
Methode gemessen, die nichts weiter tut, als eine Membervariable zu
inkrementieren. Alle beteiligten Klassen liegen in getrennten Überset-
zungseinheiten, um zu verhindern, dass der Compiler indirekte Aufrufe
wegoptimiert. Hier sind die Ergebnisse für einen Intel T7500 (2,2GHz):
1
———————————————————————————————
2
           aufgerufenes Objekt
3
           auf Stack   statisch
4
———————————————————————————————
5
6
direkt       2,5ns      2,5ns
7
8
umcasten     4,6ns      4,0ns
9
10
virtuell     8,4ns      6,0ns
11
12
function     8,4ns      3,8ns
13
———————————————————————————————
14
15
Erläuterungen:
16
17
direkt:   direkter Aufruf der Methode ohne Callback
18
19
umcasten: Aufruf über Callback mit Umcasten von Objekt- und Methoden-
20
          zeiger, d.h. ohne Benutzung virtueller Methoden
21
22
virtuell: Aufruf über Callback mit virtueller Methode (typsicher)
23
24
function: Aufruf über function<>-Objekt (typsicher)

Johann L. schrieb:
> Nehmen wir mal an, es gäbe ein <functional> im avr-g++. <functionl>
> gibt's zumindest bei avr-g++ 4.8 noch nicht (getestet mit avr-g++ 4.8
> vom 2012-09-15).
>
> Wie würde der erzeugte Code aussehen? Braucht das auch VTables?

Virtuelle Methoden und VTables werden m.W. nicht benutzt. Stattdessen
braucht man aber einen Mechanismus, um von der durch den Lambda-Ausdruck
definierten Funktion auf Variablen im Erstellungskontext (im vorliegen-
den Fall das Objekt exampleObject) zugreifen zu können. Das kostet,
wie man in obiger Tabelle sehen kann, insbesodere bei Variablen auf dem
Stack ebenfalls Zeit.

Johann L. schrieb:
> Oder sogar noch schlimmeres wie Trampolins (Entspricht den lokalen
> Funktionen von GNU-C), die auf AVR schon überhaupt nicht implementierbar
> sind — noch nicht mal ineffizient — weil es bei AVRs nicht möglich ist,
> Code auf dem Stack auszuführen.

Trampoline und dynamisch generierter Code sind nicht im Spiel, aber
mehrfache Indirektion (wie auch bei den virtuellen Methoden) schon.
Und wenn ich das vorhin im Debugger richtig gesehen habe, werden auch
dynamisch allozierte Objekte verwendet, weswegen das für kleine µCs
nicht so sehr geeignet sein dürfte.

So ganz bin ich aber bei der Implementierung dieser Closures noch nicht
durchgestiegen, da sowohl die beteiligten Deklaratioonen im functional-
Header als auch der erzeugte Assembler-Code recht wirr aussehen ;-)

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.