c++ use - Perché usare affermazioni do-while e if-else apparentemente prive di significato nelle macro?




5 Answers

Il do ... while e if ... else ci sono per farlo in modo che un punto e virgola dopo la tua macro significhi sempre la stessa cosa. Diciamo che hai qualcosa come la tua seconda macro.

#define BAR(X) f(x); g(x)

Ora se dovessi usare BAR(X); in una dichiarazione if ... else , in cui i corpi dell'istruzione if non erano racchiusi tra parentesi graffe, si ottiene una brutta sorpresa.

if (corge)
  BAR(corge);
else
  gralt();

Il codice di cui sopra si espanderebbe in

if (corge)
  f(corge); g(corge);
else
  gralt();

che è sintatticamente errato, poiché il resto non è più associato al if. Non aiuta a racchiudere le cose tra parentesi graffe all'interno della macro, poiché un punto e virgola dopo le parentesi graffe è sintatticamente errato.

if (corge)
  {f(corge); g(corge);};
else
  gralt();

Ci sono due modi per risolvere il problema. Il primo consiste nell'usare una virgola in istruzioni di sequenza all'interno della macro senza privarla della sua capacità di agire come un'espressione.

#define BAR(X) f(X), g(X)

La versione precedente di BAR espande il codice sopra in quanto segue, che è sintatticamente corretto.

if (corge)
  f(corge), g(corge);
else
  gralt();

Questo non funziona se invece di f(X) hai un corpo di codice più complicato che deve andare nel suo blocco, ad esempio per dichiarare variabili locali. Nel caso più generale la soluzione è usare qualcosa come do ... while per far sì che la macro sia una singola istruzione che prende un punto e virgola senza confusione.

#define BAR(X) do { \
  int i = f(X); \
  if (i > 4) g(i); \
} while (0)

Non devi usare do ... while , potresti anche inventare qualcosa con if ... else anche se quando if ... else espande all'interno di un if ... else porta a un " dangling else" ", che potrebbe rendere ancora più difficile trovare un problema ancora presente, come nel seguente codice.

if (corge)
  if (1) { f(corge); g(corge); } else;
else
  gralt();

Il punto è utilizzare il punto e virgola in contesti in cui un punto e virgola pendente è errato. Naturalmente, a questo punto potrebbe (e probabilmente dovrebbe) essere discusso sul fatto che sarebbe meglio dichiarare BAR come una funzione reale, non una macro.

In sintesi, il do ... while è lì per aggirare le carenze del preprocessore C. Quando quelle guide di stile C ti dicono di licenziare il preprocessore C, questo è il genere di cose di cui sono preoccupati.

in isocpp

In molti macro C / C ++ vedo il codice della macro avvolto in quello che sembra un loop senza senso. Ecco alcuni esempi

#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else

Non riesco a vedere cosa do while fa. Perché non scrivere semplicemente questo senza di esso?

#define FOO(X) f(X); g(X)



@ jfm3 - Hai una bella risposta alla domanda. Si potrebbe anche voler aggiungere che l'idioma macro previene anche il possibile comportamento non intenzionale più pericoloso (perché non c'è errore) con semplici istruzioni "if":

#define FOO(x)  f(x); g(x)

if (test) FOO( baz);

si espande a:

if (test) f(baz); g(baz);

che è sintatticamente corretto quindi non c'è nessun errore del compilatore, ma ha la conseguenza probabilmente non intenzionale che g () sarà sempre chiamato.




Mentre è previsto che i compilatori ottimizzino il do { ... } while(false); cicli, c'è un'altra soluzione che non richiederebbe quel costrutto. La soluzione è utilizzare l'operatore virgola:

#define FOO(X) (f(X),g(X))

o anche più estaticamente:

#define FOO(X) g((f(X),(X)))

Anche se funzionerà bene con istruzioni separate, non funzionerà con i casi in cui le variabili sono costruite e utilizzate come parte del #define :

#define FOO(X) (int s=5,f((X)+s),g((X)+s))

Con questo si sarebbe costretti a usare il costrutto do / while.




Per alcune ragioni non posso commentare la prima risposta ...

Alcuni di voi hanno mostrato macro con variabili locali, ma nessuno ha detto che non si può semplicemente usare un nome in una macro! Morderà l'utente un giorno! Perché? Perché gli argomenti di input sono sostituiti nel modello macro. E nei tuoi esempi macro hai usato il nome variabile probabilmente usato più comunemente io .

Ad esempio quando la seguente macro

#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)

è usato nella seguente funzione

void some_func(void) {
    int i;
    for (i = 0; i < 10; ++i)
        FOO(i);
}

la macro non utilizzerà la variabile desiderata i, che è dichiarata all'inizio di some_func, ma la variabile locale, che è dichiarata nel ciclo do ... while della macro.

Quindi, non usare mai nomi di variabili comuni in una macro!




Ho trovato questo trucco molto utile in situazioni in cui è necessario elaborare un valore particolare in modo sequenziale. Ad ogni livello di elaborazione, se si verifica qualche errore o condizione non valida, è possibile evitare ulteriori elaborazioni e scoppiare in anticipo. per esempio

#define CALL_AND_RETURN(x)  if ( x() == false) break;
do {
     CALL_AND_RETURN(process_first);
     CALL_AND_RETURN(process_second);
     CALL_AND_RETURN(process_third);
     //(simply add other calls here)
} while (0);



Related

c++ c c-preprocessor c++-faq