Domanda

Diciamo che per qualche motivo devi scrivere una macro: MACRO (X, Y) . (Supponiamo che ci sia una buona ragione per cui non puoi usare una funzione in linea.) Desideri che questa macro emuli una chiamata a una funzione senza valore di ritorno.


Esempio 1: dovrebbe funzionare come previsto.

if (x > y)
  MACRO(x, y);
do_something();

Esempio 2: questo non dovrebbe comportare un errore del compilatore.

if (x > y)
  MACRO(x, y);
else
  MACRO(y - x, x - y);

Esempio 3: questo non deve essere compilato.

do_something();
MACRO(x, y)
do_something();

Il modo ingenuo di scrivere la macro è così:

#define MACRO(X,Y)                       \
cout << "1st arg is:" << (X) << endl;    \
cout << "2nd arg is:" << (Y) << endl;    \
cout << "Sum is:" << ((X)+(Y)) << endl;

Questa è una pessima soluzione che non riesce a tutti e tre gli esempi e non dovrei spiegare perché.

Ignora ciò che effettivamente fa la macro, non è questo il punto.


Ora, il modo in cui vedo spesso le macro scritte è racchiuderle tra parentesi graffe, in questo modo:

#define MACRO(X,Y)                         \
{                                          \
  cout << "1st arg is:" << (X) << endl;    \
  cout << "2nd arg is:" << (Y) << endl;    \
  cout << "Sum is:" << ((X)+(Y)) << endl;  \
}

Questo risolve l'esempio 1, perché la macro si trova in un blocco di istruzioni. Ma l'esempio 2 è rotto perché inseriamo un punto e virgola dopo la chiamata alla macro. Questo fa pensare al compilatore che il punto e virgola sia di per sé un'istruzione, il che significa che l'istruzione else non corrisponde a nessuna istruzione if! Infine, l'esempio 3 viene compilato correttamente, anche se non è presente un punto e virgola, poiché un blocco di codice non ha bisogno di un punto e virgola.


C'è un modo per scrivere una macro in modo che passi tutti e tre gli esempi?


Nota: sto inviando la mia risposta come parte di modo accettato di condividere un suggerimento , ma se qualcuno ha una soluzione migliore sentiti libero di pubblicarlo qui, potrebbe ottenere più voti del mio metodo. :)

È stato utile?

Soluzione

Le macro dovrebbero generalmente essere evitate; preferisci le funzioni in linea a loro in ogni momento. Qualsiasi compilatore degno di questo nome dovrebbe essere in grado di incorporare una piccola funzione come se fosse una macro e una funzione incorporata rispetterà gli spazi dei nomi e altri ambiti, oltre a valutare tutti gli argomenti una volta.

Se deve essere una macro, un ciclo while (già suggerito) funzionerà, oppure puoi provare l'operatore virgola:

#define MACRO(X,Y) \
 ( \
  (cout << "1st arg is:" << (X) << endl), \
  (cout << "2nd arg is:" << (Y) << endl), \
  (cout << "3rd arg is:" << ((X) + (Y)) << endl), \
  (void)0 \
 )

(void) 0 fa sì che l'istruzione valuti in uno di tipo void e l'uso di virgole anziché di punti e virgola ne consente l'utilizzo all'interno di un'istruzione, piuttosto che solo come standalone. Vorrei comunque raccomandare una funzione in linea per una serie di motivi, il più dei quali è ambito e il fatto che MACRO (a ++, b ++) aumenterà a e b due volte.

Altri suggerimenti

Esiste una soluzione piuttosto intelligente:

#define MACRO(X,Y)                         \
do {                                       \
  cout << "1st arg is:" << (X) << endl;    \
  cout << "2nd arg is:" << (Y) << endl;    \
  cout << "Sum is:" << ((X)+(Y)) << endl;  \
} while (0)

Ora hai una singola istruzione a livello di blocco, che deve essere seguita da un punto e virgola. Questo si comporta come previsto e desiderato in tutti e tre gli esempi.

So che hai detto "ignora ciò che fa la macro", ma le persone troveranno questa domanda cercando in base al titolo, quindi penso che sia giustificata la discussione di ulteriori tecniche per emulare funzioni con le macro.

Il più vicino che conosco è:

#define MACRO(X,Y) \
do { \
    auto MACRO_tmp_1 = (X); \
    auto MACRO_tmp_2 = (Y); \
    using std::cout; \
    using std::endl; \
    cout << "1st arg is:" << (MACRO_tmp_1) << endl;    \
    cout << "2nd arg is:" << (MACRO_tmp_2) << endl;    \
    cout << "Sum is:" << (MACRO_tmp_1 + MACRO_tmp_2) << endl; \
} while(0)

In questo modo:

  • Funziona correttamente in ciascuno dei contesti indicati.
  • Valuta ciascuno dei suoi argomenti esattamente una volta, che è una caratteristica garantita di una chiamata di funzione (presupponendo in entrambi i casi nessuna eccezione in nessuna di quelle espressioni).
  • Agisce su qualsiasi tipo, usando " auto " da C ++ 0x. Questo non è ancora C ++ standard, ma non c'è altro modo per ottenere le variabili tmp richieste dalla regola di valutazione singola.
  • Non richiede al chiamante di avere nomi importati dallo spazio dei nomi std, come invece fa la macro originale, ma una funzione no.

Tuttavia, differisce ancora da una funzione in quanto:

  • In alcuni usi non validi può dare diversi errori o avvisi al compilatore.
  • Va storto se X o Y contengono usi di 'MACRO_tmp_1' o 'MACRO_tmp_2' dall'ambito circostante.
  • Relativo alla cosa std dello spazio dei nomi: una funzione usa il proprio contesto lessicale per cercare i nomi, mentre una macro usa il contesto del suo sito di chiamata. Non c'è modo di scrivere una macro che si comporta come una funzione in questo senso.
  • Non può essere usato come espressione di ritorno di una funzione nulla, che può fare un'espressione vuota (come la soluzione virgola). Questo è ancora più un problema quando il tipo di ritorno desiderato non è nullo, specialmente se usato come un valore. Ma la soluzione virgola non può includere l'uso delle dichiarazioni, perché sono dichiarazioni, quindi scegline una o usa l'estensione ({...}) GNU.

Ecco una risposta che arriva proprio dal libc6 ! Dando un'occhiata a /usr/include/x86_64-linux-gnu/bits/byteswap.h , ho trovato il trucco che stavi cercando.

Alcuni critici delle soluzioni precedenti:

  • La soluzione di Kip non consente di valutare un'espressione , che alla fine è spesso necessaria.
  • La soluzione di coppro non consente di assegnare una variabile poiché le espressioni sono separate, ma possono essere valutate come espressione.
  • La soluzione di Steve Jessop usa la parola chiave auto C ++ 11, va bene, ma sentiti libero di usare il tipo conosciuto / previsto invece

Il trucco è usare sia il costrutto (expr, expr) che un ambito {} :

#define MACRO(X,Y) \
  ( \
    { \
      register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  )

Nota l'uso della parola chiave register , è solo un suggerimento per il compilatore. I parametri macro X e Y sono (già) racchiusi tra parentesi e espressi in un tipo previsto. Questa soluzione funziona correttamente con pre e post-incremento poiché i parametri vengono valutati una sola volta.

A scopo esemplificativo, anche se non richiesto, ho aggiunto l'istruzione __x + __y; , che è il modo per rendere l'intero blocco da valutare come quella precisa espressione.

È più sicuro usare void (); se vuoi assicurarti che la macro non valuti un'espressione, quindi è illegale dove ci si aspetta un rvalue .

Tuttavia , la soluzione non è non conforme a ISO C ++ come si lamenterà g ++ -pedantic :

warning: ISO C++ forbids braced-groups within expressions [-pedantic]

Per dare un po 'di riposo a g ++ , usa (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE) in modo che la nuova definizione sia:

#define MACRO(X,Y) \
  (__extension__ ( \
    { \
      register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  ))

Per migliorare ancora di più la mia soluzione, usiamo la parola chiave __typeof__ , come visto in MIN e MAX in C :

#define MACRO(X,Y) \
  (__extension__ ( \
    { \
      __typeof__(X) __x = (X); \
      __typeof__(Y) __y = (Y); \
      std::cout << "1st arg is:" << __x << std::endl; \
      std::cout << "2nd arg is:" << __y << std::endl; \
      std::cout << "Sum is:" << (__x + __y) << std::endl; \
      __x + __y; \
    } \
  ))

Ora il compilatore determinerà il tipo appropriato. Anche questa è un'estensione gcc .

Nota la rimozione della parola chiave register , come farebbe il seguente avvertimento quando usato con un tipo di classe:

warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra]

C ++ 11 ci ha portato lambda, che può essere incredibilmente utile in questa situazione:

#define MACRO(X,Y)                              \
    [&](x_, y_) {                               \
        cout << "1st arg is:" << x_ << endl;    \
        cout << "2nd arg is:" << y_ << endl;    \
        cout << "Sum is:" << (x_ + y_) << endl; \
    }((X), (Y))

Mantieni il potere generativo delle macro, ma hai un ambito comodo da cui puoi restituire quello che vuoi (incluso void ). Inoltre, viene evitato il problema di valutare più volte i parametri macro.

Crea un blocco usando

 #define MACRO(...) do { ... } while(false)

Non aggiungere un; dopo il tempo (falso)

La tua risposta soffre del problema della valutazione multipla, quindi (es.)

macro( read_int(file1), read_int(file2) );

farà qualcosa di inaspettato e probabilmente indesiderato.

Come altri hanno già detto, dovresti evitare le macro quando possibile. Sono pericolosi in presenza di effetti collaterali se gli argomenti macro vengono valutati più di una volta. Se conosci il tipo di argomenti (o puoi utilizzare la funzione C ++ 0x auto ), puoi utilizzare i provvisori per applicare la valutazione singola.

Un altro problema: l'ordine in cui si verificano più valutazioni potrebbe non essere quello che ti aspetti!

Considera questo codice:

#include <iostream>
using namespace std;

int foo( int & i ) { return i *= 10; }
int bar( int & i ) { return i *= 100; }

#define BADMACRO( X, Y ) do { \
    cout << "X=" << (X) << ", Y=" << (Y) << ", X+Y=" << ((X)+(Y)) << endl; \
    } while (0)

#define MACRO( X, Y ) do { \
    int x = X; int y = Y; \
    cout << "X=" << x << ", Y=" << y << ", X+Y=" << ( x + y ) << endl; \
    } while (0)

int main() {
    int a = 1; int b = 1;
    BADMACRO( foo(a), bar(b) );
    a = 1; b = 1;
    MACRO( foo(a), bar(b) );
    return 0;
}

Ed è prodotto come compilato ed eseguito sulla mia macchina:

X=100, Y=10000, X+Y=110
X=10, Y=100, X+Y=110

Se sei disposto ad adottare la pratica di utilizzare sempre parentesi graffe nelle tue dichiarazioni if,

Alla tua macro mancherebbe semplicemente l'ultimo punto e virgola:

#define MACRO(X,Y)                       \
cout << "1st arg is:" << (X) << endl;    \
cout << "2nd arg is:" << (Y) << endl;    \
cout << "Sum is:" << ((X)+(Y)) << endl

Esempio 1: (compila)

if (x > y) {
    MACRO(x, y);
}
do_something();

Esempio 2: (compila)

if (x > y) {
    MACRO(x, y);
} else {
    MACRO(y - x, x - y);
}

Esempio 3: (non compilare)

do_something();
MACRO(x, y)
do_something();
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top