Jan H. schrieb:> wie castet man den funktionszeiger richtig?> es gibt verschiedene funktionszeiger also verschiedene rückgabetypen..
Casten?
Gar nicht!
Ein solcher Zeiger sollte Typsicher sein.
Also nix mit cast!
Arduino F. schrieb:> Jan H. schrieb:>> wie castet man den funktionszeiger richtig?>> es gibt verschiedene funktionszeiger also verschiedene rückgabetypen..>> Casten?> Gar nicht!> Ein solcher Zeiger sollte Typsicher sein.> Also nix mit cast!
was könnte ich stattdessen machen, wenn dort es dort mehrere
rückgabetypen gibt?
Moin,
Jan H. schrieb:> wie castet man den funktionszeiger richtig?
Wuerde ich garnicht machen. Ich halt's nicht fuer klug, dem Compiler
erst zu versichern, dass eine Funktion so rein ueberhaupt garnix
zurueckgibt, und dann tut sie's ploetzlich doch, und dann auch noch
verschiedenes...
Gruss
WK
Arduino F. schrieb:> Ansonsten, wenn wirklich, dann den Umweg über void zeiger.typedef> struct{> char id;> void *receive_function;> }lora_cmd_t;>> Oder was mit union basteln.
ich bin für vorschläge offen, wie würdet ihr das machen?
Jan H. schrieb:> was könnte ich stattdessen machen, wenn dort es dort mehrere> rückgabetypen gibt?
Das geht nicht, das Konzept ist kaputt.
Was Du stattdessen machen könntest: Den Funktionen wird ein void-Pointer
übergeben, der auf einen Puffer zeigt, und dazu ein Argument, das dessen
Größe in Bytes signalisiert. Zusätzlich noch ein weiterer Pointer auf
ein enum, das den schlussendlich rauskommenden Typ signalisiert.
Das Konzept entspricht dem eines "Variant"-Datentyps.
Das würde funktionieren, machts aber letztlich auch kaum sinnvoller,
weil der Aufrufer jeweils das "zurückgegebene" Resultat korrekt
auseinanderpflücken und einsortieren muss.
Vielleicht solltest Du nochmal genauer darüber nachdenken, welches
Problem Du eigentlich lösen möchtest.
Jan H. schrieb:> ich bin für vorschläge offen, wie würdet ihr das machen
Sag doch erstmal wie du den Zeiger aufrufen und dabei den Rückgabewert
nutzen willst...
Moin,
Jan H. schrieb:> wie würdet ihr das machen?
Bau wrapper um deine Funktionen, die verschiedene Typen zurueckgeben,
die die verschiedenen Typen auf einen Typen entsprechend wandeln.
Diese Wrapperfunktionen kannste dann ueber fktptr aufrufen.
Gruss
WK
Hey,
du willst zu einem Funktionspointer casten und nicht einfach zum
Rückgabetyp.
Also nach (void (*)(void)) und beim Aufruf der Funktion nach ((T
(*)(void))
Gruß
emko
Harald K. schrieb:> Jan H. schrieb:>> was könnte ich stattdessen machen, wenn dort es dort mehrere>> rückgabetypen gibt?>> Das geht nicht, das Konzept ist kaputt.>> Was Du stattdessen machen könntest: Den Funktionen wird ein void-Pointer> übergeben, der auf einen Puffer zeigt, und dazu ein Argument, das dessen> Größe in Bytes signalisiert. Zusätzlich noch ein weiterer Pointer auf> ein enum, das den schlussendlich rauskommenden Typ signalisiert.>> Das Konzept entspricht dem eines "Variant"-Datentyps.>> Das würde funktionieren, machts aber letztlich auch kaum sinnvoller,> weil der Aufrufer jeweils das "zurückgegebene" Resultat korrekt> auseinanderpflücken und einsortieren muss.>> Vielleicht solltest Du nochmal genauer darüber nachdenken, welches> Problem Du eigentlich lösen möchtest.
okay, ich verstehe..
es geht darum eine funktion nach erhalt eines bestimmten commands
auszuführen..
empfange ich z.b.: "toggle#0" soll halt die funktion mit der id.: 0
ausgeführt werden usw.
das war der plan dahinter.. quasi eine tabelle wo meine ganzen
funktionen vorhanden sind die dann nach "index" aufgerufen werden
können.
Jan H. schrieb:> Dergute W. schrieb:>> WK>> haste mal ein beispiel wie du das meinst?
Damit haben send_bat_wrapper() und read_bat_voltage_avg() die selbe
Geschmacksrichtung von Funktionspointer:
Jan H. schrieb:> empfange ich z.b.: "toggle#0" soll halt die funktion mit der id.: 0> ausgeführt werden usw.
Dann solltest du aber deine Datenstruktur überdenken und nicht versuchen
alles mit nur einem Funktionspointer zu lösen.
Jan H. schrieb:> was soll "T" sein? :-O
T ist ein Platzhalter für den Rückgabetyp der Funktion die aufgerufen
wird.
Jan H. schrieb:> das war der plan dahinter.. quasi eine tabelle wo meine ganzen> funktionen vorhanden sind die dann nach "index" aufgerufen werden> können.
Warum nimmst du nicht einfach einrn switch-case?
Gute programmierer setzen solche Konstrukte nur ein, wenn sie wirklich
notwendig sind.
Jan H. schrieb:> es geht darum eine funktion nach erhalt eines bestimmten commands> auszuführen..
Das ist soweit klar, warum aber sollen all' diese Funktionen einen
unterschiedlichen Rückgabewert haben?
Dergute W. schrieb:> Damit haben send_bat_wrapper() und read_bat_voltage_avg() die selbe> Geschmacksrichtung von Funktionspointer:> float send_bat_wrapper(void) {> float rc;> rc = (float)send_bat();> return rc;> }>> Gruss> WK
die beiden haben aber nicht die gleiche "geschmacksrichtung" das eine
ist uint8_t und das andere float ;)
Harald K. schrieb:> Jan H. schrieb:>> es geht darum eine funktion nach erhalt eines bestimmten commands>> auszuführen..>> Das ist soweit klar, warum aber sollen all' diese Funktionen einen> unterschiedlichen Rückgabewert haben?
also bis jetzt haben nur zwei funktionen float rückgabewerde die anderen
werden wohl uint8_t haben
FÜr sowas sind Unions gemacht. Da kann man alle Funktionszeigertypen
drin definieren und dann über das richtige Element die Funktion
aufrufen:
1
unionMeineFunktion
2
{
3
float(*floatFunktion)(void);
4
int(*intFunktion)(void);
5
};
6
7
intfunktion1(void)
8
{
9
return42;
10
}
11
12
floatfunktion2(void)
13
{
14
returnM_PI;
15
}
16
17
intmain()
18
{
19
unionMeineFunktionfkt;
20
fkt.intFuktion=funktion1;
21
fkt.intFunktion();
22
23
fkt.floatFuktion=funktion2;
24
fkt.floatFunktion();
25
}
Natürlich muss man irgendwo noch die Information haben, welcher
Funktionszeiger der richtige ist, um die Funktion entsprechend
aufzurufen (man darf nicht intFunktion befüllen und dann floatFunktion
aufrufen) und den Returnwert korrekt zu verarbeiten, aber das müsste man
beim Cast ja auch.
Arduino F. schrieb:> Ansonsten, wenn wirklich, dann den Umweg über void zeiger.
Die funktionieren zwar meistens auch, aber nur, weil die meisten
Compiler an der Stelle gegen den Standard verstoßen. Eigentlich sind
Zeiger auf Objekte und Zeiger auf Funktionen nicht kompatibel und können
auch mit einem Cast nicht einfach in einander umgewandelt werden. Wenn
man einen "generischen" Funktionszeiger in der Art nutzen möchte, sollte
man void(*)(void) verwenden.
Wie verwendest du denn die Struktur?
Ich würde beim Verwenden der Struktur anhand der ID entscheiden, welchen
Rückgabewert die Funktion hat und dann entsprechend casten (da es ja ein
Void-Pointer ist).
Also irgendwie so
typedef struct{
char id;
void (* receive_function )( void );
}lora_cmd_t;
lora_cmd_t lora_cmd[] =
{
{ '0', read_bat_voltage_avg }, // returns battery voltage
{ '1', send_bat }, // dummy
};
if(lora_cmd[0].id == 0)
{
float return_value = (float)lora_cmd[1].receive_function();
}
else
{
uint8_t return_value = (uint8_t)lora_cmd[1].receive_function();
}
Daniel V. schrieb:> if(lora_cmd[0].id == 0)> {> float return_value = (float)lora_cmd[1].receive_function();
Wie genau soll da ein void nach float gecastet werden?
Rolf M. schrieb:> Die funktionieren zwar meistens auch, aber nur, weil die meisten> Compiler an der Stelle gegen den Standard verstoßen.
Ok....
Danke!
Die Zeiger casterei ist mir sowieso suspekt!
Und dank C++ auch meist vermeidbar.
Niklas G. schrieb:> Daniel V. schrieb:>> if(lora_cmd[0].id == 0)>> {>> float return_value = (float)lora_cmd[1].receive_function();>> Wie genau soll da ein void nach float gecastet werden?
Klappt so natürlich nicht. Und nicht nur der Cast ist falsch, sondern es
wird auch noch eine Funktion über den falschen Zeigertyp aufgerufen. Es
müsste eher sowas sein:
Rolf M. schrieb:> Die funktionieren zwar meistens auch, aber nur, weil die meisten> Compiler an der Stelle gegen den Standard verstoßen. Eigentlich sind> Zeiger auf Objekte und Zeiger auf Funktionen nicht kompatibel und können> auch mit einem Cast nicht einfach in einander umgewandelt werden.
Unter POSIX-Systemen ist es erlaubt, dies ist für die Funktion dlsym()
erforderlich. Aber C und C++ an sich garantieren das nicht.
Rolf M. schrieb:> Wenn> man einen "generischen" Funktionszeiger in der Art nutzen möchte, sollte> man void(*)(void) verwenden.
Zwischen Funktionszeigern hin-und her-casten ist erlaubt, aber aufrufen
darf man ihn nur über den "richtigen" (ursprünglichen) Typ.
Rolf M. schrieb:> Oder man nimmt eben die unions und spart sich die ganze Cast-Orgie.
Ja, wobei man sich natürlich fragt: Wenn man sowieso ein switch-case für
den Rückgabetyp braucht - wozu dann noch der Funktionszeiger?
Niklas G. schrieb:> Unter POSIX-Systemen ist es erlaubt, dies ist für die Funktion dlsym()> erforderlich. Aber C und C++ an sich garantieren das nicht.
Windows hat eine vergleichbare Funktion, weshalb das da normalerweise
auch funktioniert. Wie gesagt: Die meisten Compiler erlauben es. Ich
vermeide es trotzdem, wenn ich kann.
> Rolf M. schrieb:>> Wenn>> man einen "generischen" Funktionszeiger in der Art nutzen möchte, sollte>> man void(*)(void) verwenden.>> Zwischen Funktionszeigern hin-und her-casten ist erlaubt, aber aufrufen> darf man ihn nur über den "richtigen" (ursprünglichen) Typ.
Ja, richtig. Man castet dann vom Typ der tatsächlichen Funktion auf
void(*)(void) und vor dem Aufruf wieder zurück auf den ursprünglichen
Typ. Deshalb hab ich auch geschrieben, dass man irgendwo noch die Info
braucht, was denn der ursprüngliche Typ war.
> Rolf M. schrieb:>> Oder man nimmt eben die unions und spart sich die ganze Cast-Orgie.>> Ja, wobei man sich natürlich fragt: Wenn man sowieso ein switch-case für> den Rückgabetyp braucht - wozu dann noch der Funktionszeiger?
Es kann ja auch mehrere Einträge mit dem selben Typ geben. Soweit ich
verstehe, will er eine Zuordnungstabelle haben zwischen bestimmten
Parametern und den Funktionen, um diese in irgendeiner Form zu
verarbeiten. Wenn's jetzt z.B. 100 float-Parameter gibt und 100
Funktionen dafür, muss man in der Tabelle nur die Zeiger darauf
hinterlegen und nicht im switch/case 100 separate Funktionsaufrufe dafür
haben, also nicht ein case pro Funktion, sondern nur einer pro
Funktions-Typ.
Rolf M. schrieb:> Wenn's jetzt z.B. 100 float-Parameter gibt und 100> Funktionen dafür, muss man in der Tabelle nur die Zeiger darauf> hinterlegen und nicht im switch/case 100 separate Funktionsaufrufe dafür> haben, also nicht ein case pro Funktion, sondern nur einer pro> Funktions-Typ.
Dafür erhält man einen Quelltext, der nur mühsam zu durchblicken ist.
Das geht am Sinn einer Hochsprache vorbei. Code soll für andere
Entwickler gut lesbar sein. Gut lesbarer Code vermeidet
Missverständnisse und Fehler.
100 case sind gut lesbar. Deren große Anzahl ist kein Problem bezüglich
Lesbarkeit.
Sherlock 🕵🏽♂️ schrieb:> Dafür erhält man einen Quelltext, der nur mühsam zu durchblicken ist.
Das sehe ich anders. Ich finde so eine Tabelle lesbarer, als so ein
riesiges switch/case. Und wenn pro Eintrag noch mehr als eine ID und
eine Funktion benötigt werden, ist die Lesbarkeit beim switch/case
endgültig dahin.
> 100 case sind gut lesbar. Die schiere Anzahl ist kein Problem.
Nicht so gut wie die Tabelle meiner Meinung nach.
{'0',wrap<read_bat_voltage_avg>()},// returns battery voltage
23
{'1',wrap<send_bat>()},// dummy
24
{'2',wrap<do_something>()}// A function that returns void
25
};
In wrapTypeErased kann man dann was beliebiges mit dem Rückgabetyp
machen, und weil es ein template ist hat "ret" halt immer den richtigen
Rückgabetyp der jeweiligen Funktion. Hier wird es einfach auf den stdout
ausgegeben, automatisch entsprechend formatiert. Man kann den
Rückgabewert auch ignorieren aber wozu gibt es ihn dann überhaupt?
Das Ganze kommt ganz ohne Casts aus und ist typsicher. Das
"wrap<EineFunktion> ()" wird letztlich zu einem Funktionszeiger auf eine
automatisch generierte Funktion für den richtigen Rückgabewert, welche
die eigentliche Funktion aufruft und dann hier als Beispiel den
Rückgabewert ausgibt. Als Sonderfall wird bei void-Funktionen direkt der
gewünschte Funktionszeiger verwendet, ohne Wrapper. Es wird pro
Rückgabetyp eine Funktion generiert (nicht pro Funktion).
Es gibt aber noch mehr Möglichkeiten, z.B. mit std::function oder
vielleicht auch std::variant + std::visit oder ganz klassisch
Polymorphie.
Wenn es Standard-C bleiben muss (wie z.B. ein Linux-Kernel) muss man
einen eigenen Datentyp definieren, den alle Funktionen als Ergebnis
liefern können. Typischerweise ein Struct mit einer Typ-ID und einer
eigebetteten Union.
1
struct return_typ {
2
enum { TYP_FLOAT, TYP_UINT8, TYP_STRING } typ;
3
union {
4
float float_val;
5
uint8_t uint8_val;
6
char *string_val;
7
} data;
8
};
9
10
struct return_typ read_bat_voltage_avg();
11
struct return_typ send_bat();
12
13
typedef struct{
14
char id;
15
struct return_typ (* receive_function )( void );
16
}lora_cmd_t;
17
18
lora_cmd_t lora_cmd[] =
19
{
20
{ '0', read_bat_voltage_avg }, // returns battery voltage
21
{ '1', send_bat }, // dummy
22
};
23
24
struct return_typ result = (*lora_cmd[1])();
25
switch (result.typ) {
26
case TYP_FLOAT: ... result.data.float_val;
27
case TYP_UINT8: ... result.data.uint8_val;
28
...
29
};
Evtl. wäre es notwendig einen pointer auf ein struct return_typ als
Ergebnis zu liefern.
Nikolaus S. schrieb:> Typischerweise ein Struct mit einer Typ-ID und einer> eigebetteten Union.
Kriegt man das nicht besser mit Generic selection hin? Da muss man den
Wrappercode halt für jeden Typ einzeln schreiben (statt mit dem template
in C++), aber es spart die Laufzeit-Prüfung und das Verpacken/Auspacken
im struct.
Rolf M. schrieb:> union MeineFunktion> {> float (*floatFunktion)(void);> int (*intFunktion)(void);> };> int funktion1(void)> {> return 42;> }> float funktion2(void)> {> return M_PI;> }
Dann kann ich aber nicht mit meiner "Tabelle" weiterarbeiten?
Jan H. schrieb:> Dann kann ich aber nicht mit meiner "Tabelle" weiterarbeiten?
Doch, du musst die union halt statt des Funktionszeigers in die Tabelle
eintragen.
Jan H. schrieb:>> struct return_typ result = (*lora_cmd[1])();> Was genau bewirkt diese Zeile?
Führe die Funktion aus, auf die "lora_cmd[1]" zeigt, und zwar ohne
Argumente, und lege das Ergebnis in der Variable "result" ab, die eine
Struktur vom Typ "return_typ" ist.
Nikolaus S. schrieb:> struct return_typ result = (*lora_cmd[1])();> switch (result.typ) {> case TYP_FLOAT: ... result.data.float_val;> case TYP_UINT8: ... result.data.uint8_val;> ...> };
ich habe das mal so in den online debugger gehauen und der wirft mir da
fehler, siehe foto.
Niklas G. schrieb:> Jan H. schrieb:>> ich habe das mal so in den online debugger gehauen und der wirft mir da>> fehler, siehe foto.>> Ja es muss so sein:>> 1> struct return_typ result = (lora_cmd[1].receive_function)();
Okay dann verstehe ich es.
Und das mit den cases funktioniert so auch nicht oder?
Jan H. schrieb:> Und das mit den cases funktioniert so auch nicht oder?
Na du musst halt noch was einfügen, was den Rückgabewert verarbeitet.
Und ein "break;" in jeden "case". Schreib doch mal was du mit dem
Rückgabewert überhaupt machen würdest, da könnte man dir vermutlich viel
besser helfen.
Niklas G. schrieb:> Jan H. schrieb:>> Und das mit den cases funktioniert so auch nicht oder?>> Na du musst halt noch was einfügen, was den Rückgabewert verarbeitet.> Und ein "break;" in jeden "case". Schreib doch mal was du mit dem> Rückgabewert überhaupt machen würdest, da könnte man dir vermutlich viel> besser helfen.
Es geht ja erstmal um das Prinzip.
Der Compiler findet die enums im Struct nicht.
Jan H. schrieb:> Der Compiler findet die enums im Struct nicht.
Zeig die Fehlermeldung. Es müsste eigentlich so gehen.
Jan H. schrieb:> Es geht ja erstmal um das Prinzip.
Naja, es gibt hier viele prinzipielle Möglichkeiten. Dein ursprünglicher
Wunsch passt nicht so recht auf die üblichen Prinzipien einer statisch
typisierten Sprache, insbesondere nicht von C. Daher gibt es kein
Patentrezept. Deine konkrete Anwendung besser zu verstehen könnte helfen
das Problem vielleicht ganz anders besser zu lösen (X-Y-Problem).
Am Einfachsten ist es, die Verarbeitung, die in das switch-case kommen
soll, einfach in die einzelnen Funktionen zu verschieben. Dann löst sich
das alles in Luft auf.
Niklas G. schrieb:> Am Einfachsten ist es
... die kaputte Designentscheidung über den Haufen zu werfen, Funktionen
mit unterschiedlicher Signatur gemischt verwenden zu wollen.
Jan H. schrieb:> also bis jetzt haben nur zwei funktionen float rückgabewerde die anderen> werden wohl uint8_t haben
Warum nur?
Sherlock 🕵🏽♂️ schrieb:> Man kann Werte von 0-256 durchaus auch als float zurück geben.
Nach dem Motto: Der Code ist schlecht, lasst uns ihn noch schlechter
machen!
{'0',GET_WRAPPER(read_bat_voltage_avg)},// returns battery voltage
32
{'1',GET_WRAPPER(send_bat)},// dummy
33
};
34
35
intmain(){
36
lora_cmd[0].receive_function();
37
lora_cmd[1].receive_function();
38
}
Man muss für jeden Typ eine eigene "callWrapper_XXX" -Funktion schreiben
welche das Ergebnis verarbeitet, und dies dann im DEFINE_WRAPPER in das
_Generic eintragen. Blöderweise muss man den Pointer noch mal casten,
weil bei _Generic alle Zweige gültig sein müssen, auch wenn sie nie
evaluiert werden. Dann muss man für jede der Handler-Funktionen per
GET_WRAPPER einen Wrapper definieren und diesen per GET_WRAPPER in die
Liste eintragen.
Macht im Endeffekt das Gleiche wie obige C++ -Version, aber eben in
umständlich dank C, ist aber ein bisschen effizienter als das
switch-case mit dem return_typ.
Johann L. schrieb:> Nach dem Motto: Der Code ist schlecht, lasst uns ihn noch schlechter> machen!
Das finde ich gar nicht. Wenn alle Funktionen den gleichen Rückgabetyp
haben, wäre die ganze Diskussion hier überflüssig. Man könnte das
einfach so ohne Tricks runter programmieren.
{'0',wrap<read_bat_voltage_avg>()},// returns battery voltage
21
{'1',wrap<send_bat>()}// dummy
22
};
23
24
intmain(){
25
intlokaleVariable=42;
26
27
std::visit([&](constauto&ret){
28
// Mach was mit dem Rückgabewert "ret"
29
std::cout<<"LoRa-Funktion wurde aufgerufen, Rückgabewert ist: "
30
// Hack: Nutze '+' Operator um char-Typen (inklusive (u)int8_t) nach Integer zu promoten
31
// um diese als Zahl auszugeben statt als Zeichen
32
<<+ret
33
<<", dabei ist lokaleVariable="<<lokaleVariable
34
<<std::endl;
35
},lora_cmd[0].receive_function());
36
}
Der Rückgabewert wird in ein Variant eingepackt, und beim Aufruf gibt
man ein generisches Lambda an, was dann mit dem korrekten Typ aufgerufen
wird. Der Vorteil daran ist, dass man bei jedem Aufruf einen
individuellen Codeblock angeben kann, der auch auf lokale Variablen
zugreifen kann, aber es nicht wie in C für jeden Typ manuell machen
muss. Im Endeffekt ist es das gleiche wie der Vorschlag mit dem
return_typ, aber handlicher.
Wozu das umständliche switch-case Gehampel. Der Aufrufer weiß doch, was
er auswerten will. Einfach nur eine union aus float und int als
Returnwert und gut. Für sowas sind unions schließlich gedacht.
1
#include<stdint.h>
2
3
unionfi{
4
uint8_tu8;
5
floatf;
6
};
7
8
unionfiread_bat_voltage_avg()
9
{
10
unionfir;
11
r.f=1.23;
12
returnr;
13
}
14
15
unionfisend_bat()
16
{
17
unionfir;
18
r.u8=123;
19
returnr;
20
}
21
22
typedefstruct{
23
charid;
24
unionfi(*receive_function)(void);
25
}lora_cmd_t;
26
27
lora_cmd_tlora_cmd[]=
28
{
29
{'0',read_bat_voltage_avg},// returns battery voltage
Ich kann mir noch nicht so recht vorstellen, wie das nachher aussehen /
verwendet werden soll. Sind das quasi Requests, die immer genau eine
Response haben, und dort ist immer nur 1 Wert drin? Dann kann man das
eventuell machen.
Andernfalls hätte man normalerweise irgend eine Funktion, die eine
Nachricht zurück sendet, die man dann halt aufruft, und die das dann in
eine Queue packt.
Wie dem auch sei, wie wäre es mit einer tagged Union?
Jan H. schrieb:> Und das mit den cases funktioniert so auch nicht oder?
1
>structreturn_typresult=(*lora_cmd[1])();
2
>switch(result.typ){
3
>caseTYP_FLOAT:...result.data.float_val;
4
>caseTYP_UINT8:...result.data.uint8_val;
5
>...
6
>};
Ich verstehe immer noch nicht, welches Problem du so lösen willst.
Spätestens wenn es an die Auswertung der Rückgabewerte geht, braucht's
doch eh ein if-else oder switch-case?
Also warum dann nicht einfach:
1
switch(lora_id)
2
{
3
caseLORA_ID_DIES:
4
{
5
uint8_tval8=lora_mach_dies();
6
// Eval val8...
7
break;
8
}
9
caseLORA_ID_JENES:
10
{
11
floatf=lora_mach_jenes();
12
// Eval f...
13
break;
14
}
Ist das mit einer Union und Funktionszeiger-Tabelle so viel effizienter?
Oder so viel übersichtlicher?
Kaum.
Johann L. schrieb:> Kaum.
Mir schmeckt die id (bei dir lora_id) auch nicht.
Alleine schon weil man min. 2 Stellen im Code hat, die man zueinander
konsistent halten muss.
Einmal im Array, und dann in dem switch/case
Vertut man sich da, kann das zu recht schwer zu findenden Fehlern
führen.
Schöner ist es, wenn der Compiler die Chance hat da einen Irrtum
anzumeckern.
Jan H. schrieb:> es geht darum eine funktion nach erhalt eines bestimmten commands> auszuführen..>> empfange ich z.b.: "toggle#0" soll halt die funktion mit der id.: 0> ausgeführt werden usw.
Ich würde mir einen Parser schreiben der den seriellen Empfang, oder
woher auch immer das kommt, aufdrösselt in das Kommando 'toggle' und den
weiteren Parameter '0'. Dann hat man 2 Parameter die man auf alles
vergleichen kann was man möchte. Ist irgendein Vergleich 'true' wird
entsprechend irgendwas aufgerufen oder verarbeitet oder was weiß ich.
Am Ende kann man sonstwas Empfangen solange das Protokoll passt und
verarbeiten.
Meiner Meinung nach ist die Verwendung von Funktionspointern bei
unterschiedlichen Rückgabewerten ein kaputtes Konzept.
Begründung
Funktionspointer sind dafür da, gezielt eine Funktion aus einer Familie
von Funktionen aufzurufen, die das gleiche Interface benutzen. Nur hier
kann man die Parameter und Rückgabewerte gleich behandeln.
Bei unterschiedlichen Interfaces wie zum Beispiel unterschiedlichen
Rückgabewerten wird immer der Kontext relevant, wie der Rückgabewert
zu behandeln ist. Das heißt: Spätestens nach Aufruf der Funktion wird
eine Bewertung des Kontextes notwendig, um den Rückgabewert richtig
interpretieren zu können. Dann kann man auch die Funktion in der
Kontextbehandlung direkt aufrufen statt vorher abstrakt über einen
Pointer zu gehen.
Fazit
Nimm einen Case-Switch und rufe hier die Funktionen konkret auf. Dort
kannst Du dann auch den Rückgabewert korrekt (d.h. im Kontext)
auswerten.
Frank M. schrieb:> Meiner Meinung nach ist die Verwendung von Funktionspointern bei> unterschiedlichen Rückgabewerten ein kaputtes Konzept.>> Begründung>> Funktionspointer sind dafür da, gezielt eine Funktion aus einer Familie> von Funktionen aufzurufen, die das gleiche Interface benutzen. Nur hier> kann man die Parameter und Rückgabewerte gleich behandeln.> Nimm einen Case-Switch und rufe hier die Funktionen konkret auf. Dort> kannst Du dann auch den Rückgabewert korrekt (d.h. im Kontext)> auswerten.
Ich verstehe Ihren Standpunkt, aber manchmal sind Funktionszeiger
nützlich, wenn Sie eine von mehreren Funktionen zur Ausführung auswählen
müssen. In diesem Fall ist es wichtig, die richtige Schnittstelle zu
wählen. Wenn die Rückgabewerte jedoch unterschiedlich sind, kann dies
wirklich zu einem Problem werden.
Jan H. schrieb:> wie castet man den funktionszeiger richtig?
Es spricht nichts dagegen, Unions, void-Rückgabewerte oder irgendwas zu
verwenden. Casten ist aber auch möglich. Am einfachsten mit Typedefs:
1
typedeffloat(*pFloatFncType)(void);
2
typedefuint8_t(*pu8FncType)(void);
3
typedefvoid(*pVoidFncType)(void);
4
5
floatread_bat_voltage_avg(void);
6
uint8_tsend_bat(void);
7
typedefstruct{
8
charid;
9
pVoidFncTypefnc;
10
}lora_cmd_t;
11
12
lora_cmd_tlora_cmd[]=
13
{
14
{'0',(pVoidFncType)read_bat_voltage_avg},
15
{'1',(pVoidFncType)send_bat},
16
};
17
18
lora_cmd_tcmd=lora_cmd[x];
19
floatf;
20
uint8_tu8;
21
22
switch(cmd.id)
23
{
24
case0:f=((pFloatFncType)cmd.fnc)();
25
break;
26
case1:u8=((pu8FncType)cmd.fnc)();
27
break;
28
}
Mag sein, dass ich ein * oder sowas vergessen habe, aber prinzipiell
geht das. (pVoidFncType ist nicht notwendig, aber vielleicht ein
Hinweis, dass man in jedem Fall den richtigen cast auswählen muss)
Rocco F. schrieb:> Wenn die Rückgabewerte jedoch unterschiedlich sind, kann dies> wirklich zu einem Problem werden.
Darum dreht es sich hier doch die ganze Zeit, von Anfang an.
Das ist das, was an der Idee hier kaputt ist. Nicht die Verwendung von
Funktionspoinern, sondern die Verwendung von Funktionspointern mit
inkompatibler Funktionssignatur.
Und bislang wurde auch noch nicht erwähnt, welchen tieferen Sinn es
haben soll, daß die verwendeten Funktionen unterschiedliche
Rückgabetypen haben.
So viel Aufwand, wie hier darauf gelegt wurde, Krücken zu basteln, um
diese kaputte Idee irgendwie zu ermöglichen, so WENIG Aufwand wurde vom
Threadstarter betrieben, auch nur einen einzigen Erklärungsversuch zu
bringen. Mehr als "ist so" kam nicht.
Harald K. schrieb:> Nicht die Verwendung von> Funktionspoinern, sondern die Verwendung von Funktionspointern mit> inkompatibler Funktionssignatur.
Ein guter Einsatz von Funktionspointern ist, wenn beim Init oder
Konfigurieren (oder in der Statemachine) die (nächste) Funktion gesetzt
wird und der Aufrufer einfach den Ptr aufruft.
Wenn (wie hier) ein Eintrag aus einer Liste gewählt wird, dann ist das
aufwändiger und nicht mehr sooo smart. Auf den case für den Cast kommt
es dann auch nicht mehr an. Meist kann das ganze Konstrukt durch plain
Code vereinfacht werden.