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
Timert;
2
3
// Prototyp timer-funktion
4
// int Timer::after(long period, void (*callback)())
5
6
voidsendCommand(){...}
7
8
voidconnect(){
9
t.after(1000,sendCommand);
10
}
11
12
voidsetup(){
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
classPumpe
2
{
3
public:
4
Pumpe();
5
voidconnect();
6
voidsendCommand();
7
};
Pumpe.cpp:
1
#include"Pumpe.h"
2
#include"Timer.h"
3
4
Timert;
5
6
voidPumpe::sendCommand(){...}
7
8
voidPumpe::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
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.
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?
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).
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.
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?
// int Timer::after(long period, void (*callback)())
5
6
7
voidPumpe::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
Timert;
2
3
// Prototyp timer-funktion
4
// int Timer::after(long period, void (*callback)())
5
6
7
voidPumpe::sendCommand(){...}
8
9
voidPumpe::connect()
10
{
11
t.after(1000,sendCommandHelper);
12
}
13
14
PumpethePumpe;// die einzig wahre Pumpe
15
// hier als globales Objekt ausgeführt
16
17
voidsendCommandHelper(){
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.
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?
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.
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?
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
classPumpeProxy:publicEventCallbackObject
2
{
3
public:
4
PumpeProxy(Pumpe&pump,void(Pumpe::*method)()):
5
pump_(pump),method_(method){}
6
virtualvoidcallback(){(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.
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
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.
Klaus Wachtler schrieb:> ... werden es auch oft ...
Na ja.
Die C# Delegates sind schon deutlich eleganter als C++
Member-Funktionspointer.
Muss man neidlos zugeben.
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.
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
classCallbackBase{
5
public:
6
virtualvoidoperator()()=0;
7
};
8
9
template<classC>
10
classCallback:publicCallbackBase{
11
public:
12
Callback(C&object,void(C::*method)()):
13
object_(object),method_(method){}
14
virtualvoidoperator()(){(object_.*method_)();}
15
16
private:
17
C&object_;
18
void(C::*method_)();
19
};
Anwendungsbeispiel:
1
#include<iostream>
2
#include"callback.h"
3
4
usingnamespacestd;
5
6
// Event kennt nur die allgemeine Basisklasse CallbackBase und ist deswegen
7
// ebenfalls allgemein einsetzbar
8
classEvent{
9
public:
10
voidsetCallback(CallbackBase&cb){callback_=&cb;}
11
voidupdate(){
12
/*...*/
13
(*callback_)();
14
/*...*/
15
}
16
17
private:
18
CallbackBase*callback_;
19
};
20
21
// Klasse, deren Methode als Callback aufgerufen werden soll
22
classExampleClass{
23
public:
24
voidshow(){cout<<"Object of class ExampleClass"<<endl;}
25
};
26
27
intmain(){
28
Eventevent;
29
ExampleClassexampleObject;
30
31
// Anlegen eines Callback-Objekts für die Methode show des Objekts
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:
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:
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.
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
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"
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.
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.
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:>
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.
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 ;-)