Moin!
Ich hab da mal ne Frage zu Funktionspointern in C++. Und zwar speziell
zu Pointern auf Memberfunktionen einer Klasse.
Konkret brauche ich eine Timerklasse, welche eine Callbackfunction
enthält. Das Aufrufen der Callbackfunktion für globale oder statische
Funktionen ist ja kein Problem. Aber die mach ich das, wenn ich eine
Memberfunktion aufrufen will? Die Memberfunktion bekommt ja unter der
Haube bei einem Aufruf einen This-Pointer mit übergeben.
Ok, ich muss also in meiner Timerklasse den Pointer auf das Objekt und
den Pointer auf die Funktion speichern. Ich bin mir aber noch nicht
sicher, wie der Funktionszeiger und der Funktionsaufruf im Detail
aussehen muss. Die Callbackfunktion soll keine weiteren Parameter
haben:
void BeliebigeKlasse::aufzurufendeFunktion()
{
tuWasSchönes();
}
In der Timer Deklaration:
1
void* object;
2
void (*callback)(void*); // Kann man den so deklarieren? der
3
// this-Pointer taucht ja eigentlich
4
// in der Funktionssignatur gar nicht auf?
Callback Aufruf:
1
callback(object); // wird nicht funktionieren, da ich ja das, was der
2
// This-Pointer sein soll, nicht explizit der Funktion
3
// übergeben kann
Andere Möglichkeit:
1
void* object;
2
void (*callback)();
3
4
...
5
6
object->callback(); //wird wohl auch nicht funktionieren, da man einen void-Zeiger nicht dereferenzieren kann.
Ziel der Sache ist, in die Timerklasse keine Abhängigkeiten von der
Klasse, aus der die Memberfunktion stammt, reinzubringen. Der Timer soll
mit jeder beliebigen Memberfunktion (mit passender Signatur) jeder
beliebigen Klasse funktionieren.
Das geht nicht, da zum Aufruf einer nichtstatischen Memberfunktion einer
Klasse der this-Zeiger bekannt sein muss.
Den kannst Du natürlich als opaken void-Pointer an eine statische
Memberfunktion übergeben, die wiederum mit einem typecast daraus einen
korrekten this-Zeiger erzeugen und dann eine beliebige nichtstatische
Funktion aufrufen kann.
Peter Kröner schrieb:
> Moin!>> Ich hab da mal ne Frage zu Funktionspointern in C++. Und zwar speziell> zu Pointern auf Memberfunktionen einer Klasse.
Au weh.
Einer der wunden Punkte in C++
>
1
> void* object;
2
> void (*callback)(void*); // Kann man den so deklarieren? der
3
> // this-Pointer taucht ja eigentlich
4
> // in der Funktionssignatur gar nicht auf?
5
>
Der Klassenname der aufzurufenden Klasse ist Teil der Funktionssignatur.
typedef void (* MyClass::funcPtr)( void );
Aber so richtig befriedigend ist das nicht. Besser ist diese
Vorgehensweise:
Man schafft sich eine Interface-Callback Klasse.
1
classTimerCallback
2
{
3
public:
4
virtualvoidDoWork()=0;
5
};
Im Timerobjekt wird dann ein Pointer auf das tatsächliche Objekt
gehalten, welches dieses Interface implementieren muss. Damit kennt die
Timerklasse das Objekt, welches benachrichtigt werden soll (wenn auch
nur in Form eines Interfaces) und die Klasse, welche sich beim Timer um
einen Rückruf bewirbt muss lediglich von dieser TimerCallback Klasse
abgeleitet werden und die vorgeschriebene DoWork Funktion
implementieren.
1
classTimer
2
{
3
public:
4
Timer():callBack(NULL){}
5
6
voidRegisterCallback(TimerCallback*object);
7
voidPerformCallback()
8
{
9
if(callBack)
10
callBack->DoWork();
11
}
12
13
protected:
14
TimerCallback*callBack;
15
};
16
17
classMyClass:publicMyBaseClass,
18
publicTimerCallback// <- das verleiht MyClass die
19
// Fähigkeit als Callbackobjekt
20
// fuer Timer zu fungieren
21
{
22
public:
23
24
voidSomething(Timer&timer)
25
{
26
timer.RegisterCallback(this);
27
}
28
29
virtualvoidDoWork()// der Compiler wacht darüber, dass diese Funktion
Das mag zeigen, dass Zeiger auf Memberfunktionen in C++ so gut wie nie
benutzt werden. Es gibt praktisch immer eine andere, bessere
Möglichkeit.
(Alte C++ Weisheit: Jedes Problem kann mit einer zusätzlichen Klasse
mehr gelöst werden :-)
Karl heinz Buchegger schrieb:
> Das mag zeigen, dass Zeiger auf Memberfunktionen in C++ so gut wie nie> benutzt werden.
Ich habe das tatsaechlich nur einmal in 20 Jahren gebraucht und zwar um
C++-Methoden fuer X11-Callbacks zu benutzen, ohne statische
Proxy-Funktionen zu verwenden.
Bei der Variante von Karl heinz Buchegger kann man dann ja nur eine
Callback-Funktion pro Klasse definieren. Fällt euch was dazu ein, wie
man das umgehen könnte?
Peter Kröner schrieb:
> Bei der Variante von Karl heinz Buchegger kann man dann ja nur eine> Callback-Funktion pro Klasse definieren. Fällt euch was dazu ein, wie> man das umgehen könnte?
Innerhalb der Callback Funktion weiterverteilen?
Ich meine, wenn ich in einer Klasse 2 Timer mit unterschiedlichen
Intervallen verwenden möchte.
ok, dann kann man immer noch einen Timer mit höherer Frequenz nehmen,
und dann in der Callbackfunktion mitzählen. Aber mir gehts im Moment
darum, dass die Timerklasse so zu bauen, dass sie möglichst einfach und
universell einsetzbar ist.
Peter Kröner schrieb:
> Ich meine, wenn ich in einer Klasse 2 Timer mit unterschiedlichen> Intervallen verwenden möchte.
Proxyklassen verwenden, die den Callback an zwei verschieden benamste
Callbacks in der Zielklasse weiterleiten. In Java geht das elegant und
kurz mit Inner-Classes, ob das C++ schon geht, weiss ich im Moment gar
nicht.
Peter Kröner schrieb:
> Ich meine, wenn ich in einer Klasse 2 Timer mit unterschiedlichen> Intervallen verwenden möchte.
Eine Möglichkeit ist, dass der jeweils den Callback auslösende Timer
sich selbst als Argument mitgibt.
Peter Stegemann schrieb:
> Peter Kröner schrieb:>> Ich meine, wenn ich in einer Klasse 2 Timer mit unterschiedlichen>> Intervallen verwenden möchte.>> Proxyklassen verwenden, die den Callback an zwei verschieden benamste> Callbacks in der Zielklasse weiterleiten. In Java geht das elegant und> kurz mit Inner-Classes, ob das C++ schon geht, weiss ich im Moment gar> nicht.
Müsste gehen.
Die call back-Funktion bekommt doch einen void*-Parameter.
Wie schon gesagt kann man das nutzen, um einer statischen
Methode, die man als call back angibt, einen Zeiger auf das
aktuelle Objekt mitgibt.
Diese statische Methode wiederum castet dann den Zeiger in
einen auf das Objekt und ruft eine normale Methode der Klasse
für dieses Objekt auf.
Das ist zwar etwas von hinten durch die Brust in den Tränensack,
aber so ist das nun mal, wenn man Nicht-OO wie eine
call back-Routine mit Objekten koppelt.
Peter Stegemann schrieb:
> ...> Proxyklassen verwenden, die den Callback an zwei verschieden benamste> Callbacks in der Zielklasse weiterleiten. In Java geht das elegant und> kurz mit Inner-Classes, ob das C++ schon geht, weiss ich im Moment gar> nicht.
sischer dat
Ja, das mit der Template-Klasse, die den Aufruf über eine überladene
virtuelle Methode weiterreicht, ist genial!
Damit hab ich die volle Flexibilität, wie ich sie mir am Anfang mit den
Funktionspointern vorgestellt hab. Typsicher und sauber getrennt ist es
auch, was will man mehr!
C++ is manchmal gar nicht so einfach. Auf sowas wäre ich nie gekommen.
Wieder was gelernt ;) Danke an alle!
yalu schrieb:
> Das Ganze ist typsicher und erlaubt trotzdem eine saubere Trennung> zwischen Aufrufer und Aufgerufenem.
Der Code ist extrem muehsam zu lesen. Musst du fuer Zeilenumbrueche
extra bezahlen?
Peter Stegemann schrieb:
> Der Code ist extrem muehsam zu lesen. Musst du fuer Zeilenumbrueche> extra bezahlen?
Stimmt, jetzt seh' ich's auch ;-)
Vor allem bei diesen Zeilen
fällt es sogar mir nicht leicht, die Einzelteile visuell zu gruppieren.
Bitte entschuldige meine schlampige Schreibweise. Das Ding ist in
leichter Eile und inkrementell entstanden. Ich werde mir beim nächsten
Mal aber mehr Mühe geben.
Klaus Wachtler schrieb:
> hm, ich hätte nix zu meckern an der Optik.> Ist vielleicht auch ein Stück weit Geschmackssache?
Das Argument wird immer gerne gebracht. "Live" lasse ich mir dann
normalerweise mal die "paar Zeilen" erklaeren - da aendert sich der
Geschmack dann meist schnell.
Jetzt brauchst du noch eine weitere von CallbackBase abgeleitete Klasse
für statische/globale Funktionen, und der Unterschied zwischen
Member-Funktionen und statischen Funktionen verschwindet ganz.
Wenn du noch ein paar Templates für Parametertypen und Rückgabewerte
hinzufügst hast du libsigc (http://libsigc.sourceforge.net/) neu
implementiert.
Vielen Dank .. hab ewig nach einem gut erklärten Beispiel gesucht, denn
das meiste, was man findet ist nicht unbedingt spontan verständlich.
Doch ich denke jetzt hab ichs soweit - zumindest funktioniert es.
Hat diese Frage auch nur irgendetwas mit dem Thema des jetzt auch
schon vier Jahre alten Threads zu tun?
Und was soll "wie deklariere ich if" bedeuten? if ist nichts, was
deklariert werden müsste, geschweige denn deklariert werden könnte.
Gut, dass du std::function ins Spiel bringst. So ist es richtig. In
modernem C++ wird man dein main() aber mit Lambda-Ausdrücken schreiben
wollen:
1
intmain()
2
{
3
Xx;
4
x.i=23;
5
Xx2;
6
x2.i=4711;
7
Timert([&](){x.function();});
8
Timert2([&](){x2.function();});
9
Timert3(&fun);// evtl auch hier, aber ich bin mir nicht sicher
10
t.mach();
11
t2.mach();
12
t3.mach();
13
}
Der Grund ist, dass std::function schon einen indirekten Funktionsaufruf
enthält, und dann das std::bind-Konstrukt einen weiteren. Mit dem
Lambda-Ausdruck kann man diesen zweiten vermeiden. Im Gegenzug muss man
der Timer-Klasse einen template-Konstruktor verpassen: