Hallo,
Wir haben sehr viele stellen in unserem Code wo immer wieder solch ein
Konstrukt verwendet wird um eine Funktion aufzurufen.
1
if(err==ERR_OK)
2
{
3
err=foo();
4
}
Ich wollte das jetzt mit einem Makro leserlicher machen.
1
#define CALL(func) \
2
if (err == ERR_OK){ \
3
err = func; \
4
} \
Dann kann ich ja so den Aufruf machen.
1
CALL(foo());
Mein Kollege mag Makros nicht! Und findet diese gefährlich wie auch
immer. Seht ihr hier irgendwelche Nachteile?
Funktional ist das doch das selbe - Auch die Codegröße ist die gleiche!
Vielen Dank.
Außer dass ich mir einen anderen Namen als CALL aussuchen würde sollte
das kein Problem sein. Ja, mit Makros kann man ziemlichen Mist bauen,
muss man aber nicht...
Zumindest würde ich auch den Namen deutlich aussagekräftiger machen.
Zudem arbeitet das Makro mit zwei Dingen:
- der Funktion
- der Fehlervariablen
Erstens wäre es flexibler, zweitens wesentlich prägnanter für den Leser,
wenn man dies beides als Parameter übergibt.
Weiterhin sieht ein Makroaufruf aus wie eine ausführbare Anweisung.
Damit sie das auch weiterhin ist (ggf. mit überflüssigen Semikolon,
später mal einem if davor und einem else dahinter oder anderen
Spielereien), würde ich noch die do-while-Konstruktion rumbauen.
Deshalb würde ich vorschlagen:
1
#define CALL(function,error) \
2
do \
3
{ \
4
if (error == ERR_OK) \
5
{ \
6
error = function(); \
7
} \
8
} while( 0 )
Konsequenterweise könnte man ERR_OK vielleicht auch noch als
Makroparameter durchschieben, hängt davon ab...
Der gleichwertige Aufruf wäre dann:
1
CALL_FUN_IF_ERR_OK(foo,err);
Evtl. könnte man das alles aber auch weglassen.
Wenn es nämlich nur darum geht, mehrere Funktionsaufrufe zu machen, ohne
mehrfach auf Fehler reagieren zu müssen, könnte man auch ganz ohne
Makros so vorgehen:
Oliver schrieb:> Mach eine richtige Funktion daraus, kein Makro, und überlassl dem> Compiler mögliches inlining.
Das ist aber nur praktikabel, wenn die aufzurufenden Funktionen alle
keine Parameter (oder zumindest die gleiche Parameterliste haben), sonst
muß man sich nachher womöglich Dutzende solcher Wrapper-Funktionen
schreiben.
Natürlich sehr lobenswert :-)
Wobei ich leider vergessen hatte, das Makro in der Definition von CALL
auf CALL_FUN_IF_ERR_OK umzubenennen, aber das habt ihr wohl schon selber
registriert.
clonephone82 schrieb:> Achso das mit dem do{ while(0) verstehe ich noch nicht ganz ich habs mit> und ohne probiert funktioniert.> Was ist hier der Sinn?
Angenommen, du hast dein Original-Makro.
Dann schreibst du hin:
1
if(blabla)CALL(f())
2
elseputs("kein blabla..");
Dann erwartest du, daß entweder das CALL gemacht wird, oder das puts().
Tatsächlich macht der Präprozessor daraus aber:
1
if(blabla)if(err==ERR_OK){
2
err=f();
3
}
4
elseputs("kein blabla..");
Wenn man das richtig einrückt, sieht es so aus:
1
if(blabla)
2
if(err==ERR_OK)
3
{
4
err=f();
5
}
6
elseputs("kein blabla..");
D.h. das else gehört jetzt nicht mehr zum ersten if, zu dem du es haben
möchtest, sondern zum if aus dem Makro.
Der Compiler übersetzt das, aber du weisst nicht warum das Programm
Unfug macht.
Ähnliche Fälle lassen sich noch mehr konstruieren.
Mit dem do...while(0) wird aus allem innerhalb etwas in sich
abgeschlossenes, was keine solchen Nebeneffekte mehr erzeugen sollte.
Sieht doof aus, hilft aber.
Bronco schrieb:> Gefällt mir persönlich besser...
Das Problem dabei ist, dass du auf die Disziplin des Makro-*Anwenders*
vertrauen musst, während das do...while(0) die Sache auf Ebene des
Makro-*Autors* löst (der ja schließlich den potenziellen Seiteneffekt
des Makros kennt).
Jörg Wunsch schrieb:> während das do...while(0) die Sache auf Ebene des> Makro-*Autors* löst
genau
Bronco schrieb:> indem man immer> geschweifte Klammern um die Blöcke schreibt, auch wenn sie nur eine> Anweisung enthalten.> ...> Gefällt mir persönlich besser...
genau
-> es schadet ja nichts, beides zu machen
Klaus Wachtler schrieb:> Jörg Wunsch schrieb:>> während das do...while(0) die Sache auf Ebene des>> Makro-*Autors* löst>> genau>> Bronco schrieb:>> indem man immer>> geschweifte Klammern um die Blöcke schreibt, auch wenn sie nur eine>> Anweisung enthalten.>> ...>> Gefällt mir persönlich besser...>> genau>> -> es schadet ja nichts, beides zu machen
Kann man aber nur dann, wenn man auch beide Rollen hat ;-)
Bitte reduzieren Sie die Anzahl der Zitatzeilen.
Bitte reduzieren Sie die Anzahl der Zitatzeilen.
Weitere Möglichkeit ist, CALL als variadisches Makro zu schreiben (C99)
und einen bewerteten Block zu verwenden.
Damit lassen sich Aufrufe schachteln und ganz genauso wie normale
C-Ausdrücke verwenden:
1
externintfi(int);
2
externintfc(char);
3
externintfv(void);
4
externintfp(constchar*,...);
5
6
#define CALL(F, ERR, ARGS...) \
7
({ \
8
int _e = ERR; \
9
if (_e) \
10
_e = F (ARGS); \
11
_e; \
12
})
13
14
15
intcall(inti,charc,interror)
16
{
17
error=CALL(fi,error,i);
18
error=CALL(fc,error,c);
19
error=CALL(fv,error);
20
error=CALL(fi,CALL(fi,error,i),i);
21
22
if(CALL(fp,error,"%s",i)
23
||CALL(fp,error,"%c",c,i))
24
return0;
25
26
returnerror;
27
}
Damit bleibt zumindest die Quelle leserlich und sieht genauso aus wie
eine C-Quelle, zudem muß der Eingabeparameter ERR kein Lvalue sein.
Neben der Möglickkeit, ein allgemeines CALL-Makro zuverwenden kann man
auch für jeden Callee ein eigenes Makro zur Verfügung stellen, daß den
ersten Parameter wie ERR verwendet und die verbleibenden an den Callee
reicht.