Macros são partes de texto copiadas / coladas que o pré-processador colocará no código original; o autor da macro espera que a substituição produza código válido.
Existem três boas "dicas" para ter sucesso nisso:
Ajude a macro a se comportar como código genuíno
O código normal geralmente termina com um ponto e vírgula. O usuário deve visualizar o código sem precisar de um ...
doSomething(1) ;
DO_SOMETHING_ELSE(2) // <== Hey? What's this?
doSomethingElseAgain(3) ;
Isso significa que o usuário espera que o compilador produza um erro se o ponto e vírgula estiver ausente.
Mas o verdadeiro motivo realmente bom é que, em algum momento, o autor da macro talvez precise substituir a macro por uma função genuína (talvez embutida). Portanto, a macro deve realmente se comportar como uma.
Portanto, devemos ter uma macro precisando de ponto e vírgula.
Produzir um código válido
Como mostrado na resposta do jfm3, às vezes a macro contém mais de uma instrução. E se a macro for usada dentro de uma instrução if, isso será problemático:
if(bIsOk)
MY_MACRO(42) ;
Essa macro pode ser expandida como:
#define MY_MACRO(x) f(x) ; g(x)
if(bIsOk)
f(42) ; g(42) ; // was MY_MACRO(42) ;
A g
função será executada independentemente do valor de bIsOk
.
Isso significa que precisamos adicionar um escopo à macro:
#define MY_MACRO(x) { f(x) ; g(x) ; }
if(bIsOk)
{ f(42) ; g(42) ; } ; // was MY_MACRO(42) ;
Produzir um código válido 2
Se a macro é algo como:
#define MY_MACRO(x) int i = x + 1 ; f(i) ;
Podemos ter outro problema no seguinte código:
void doSomething()
{
int i = 25 ;
MY_MACRO(32) ;
}
Porque expandiria como:
void doSomething()
{
int i = 25 ;
int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}
Esse código não será compilado, é claro. Então, novamente, a solução está usando um escopo:
#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) ;
}
O código se comporta corretamente novamente.
Combinando ponto-e-vírgula + efeitos de escopo?
Há um idioma C / C ++ que produz este efeito: O loop do / while:
do
{
// code
}
while(false) ;
O do / while pode criar um escopo, encapsulando o código da macro, e precisa de um ponto e vírgula no final, expandindo para o código que precisa de um.
O bônus?
O compilador C ++ otimizará o loop do / while, pois o fato de sua pós-condição ser falsa é conhecido no momento da compilação. Isso significa que uma macro como:
#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.
}
expandirá corretamente como
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.
}
e é então compilado e otimizado como
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
{
f(43) ; g(43) ;
}
// Etc.
}
void
tipo no final ... como ((void) 0) .