Makra to kopiowane / wklejane fragmenty tekstu, które procesor umieści w oryginalnym kodzie; autor makra ma nadzieję, że zamiana wygeneruje prawidłowy kod.
Istnieją trzy dobre „wskazówki”, aby odnieść sukces:
Pomóż makrze zachowywać się jak prawdziwy kod
Normalny kod jest zwykle zakończony średnikiem. Czy użytkownik powinien wyświetlić kod, który go nie potrzebuje ...
doSomething(1) ;
DO_SOMETHING_ELSE(2) // <== Hey? What's this?
doSomethingElseAgain(3) ;
Oznacza to, że użytkownik oczekuje, że kompilator wygeneruje błąd, jeśli średnik jest nieobecny.
Ale naprawdę dobrym powodem jest to, że w pewnym momencie autor makra będzie musiał zastąpić makro prawdziwą funkcją (być może wbudowaną). Tak więc makro powinno naprawdę zachowywać się jak jedno.
Powinniśmy więc mieć makro wymagające średnika.
Utwórz poprawny kod
Jak pokazano w odpowiedzi jfm3, czasami makro zawiera więcej niż jedną instrukcję. A jeśli makro zostanie użyte w instrukcji if, będzie to problematyczne:
if(bIsOk)
MY_MACRO(42) ;
To makro można rozwinąć jako:
#define MY_MACRO(x) f(x) ; g(x)
if(bIsOk)
f(42) ; g(42) ; // was MY_MACRO(42) ;
g
Funkcja zostanie wykonana niezależnie od wartości bIsOk
.
Oznacza to, że musimy dodać zakres do makra:
#define MY_MACRO(x) { f(x) ; g(x) ; }
if(bIsOk)
{ f(42) ; g(42) ; } ; // was MY_MACRO(42) ;
Utwórz poprawny kod 2
Jeśli makro przypomina:
#define MY_MACRO(x) int i = x + 1 ; f(i) ;
Możemy mieć inny problem w następującym kodzie:
void doSomething()
{
int i = 25 ;
MY_MACRO(32) ;
}
Ponieważ rozwinąłby się jako:
void doSomething()
{
int i = 25 ;
int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}
Oczywiście ten kod się nie skompiluje. Zatem ponownie rozwiązanie wykorzystuje zakres:
#define MY_MACRO(x) { int i = x + 1 ; f(i) ; }
void doSomething()
{
int i = 25 ;
{ int i = 32 + 1 ; f(i) ; } ; // was MY_MACRO(32) ;
}
Kod znów działa poprawnie.
Łącząc efekty średnika + zakresu?
Jest jeden idiom C / C ++, który wywołuje ten efekt: Pętla do / while:
do
{
// code
}
while(false) ;
Do / while może utworzyć zakres, w ten sposób enkapsulując kod makra, i na końcu potrzebuje średnika, przekształcając się w kod, który go potrzebuje.
Bonus?
Kompilator C ++ zoptymalizuje pętlę do / while, ponieważ fakt, że jego stan końcowy jest fałszywy, jest znany w czasie kompilacji. Oznacza to, że makro takie jak:
#define MY_MACRO(x) \
do \
{ \
const int i = x + 1 ; \
f(i) ; g(i) ; \
} \
while(false)
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
MY_MACRO(42) ;
// Etc.
}
rozwinie się poprawnie jako
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
do
{
const int i = 42 + 1 ; // was MY_MACRO(42) ;
f(i) ; g(i) ;
}
while(false) ;
// Etc.
}
a następnie jest kompilowany i optymalizowany jako
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
{
f(43) ; g(43) ;
}
// Etc.
}
void
typu na końcu ... jak ((void) 0) .