Pergunta

Vamos dizer que por algum motivo você precisa escrever uma macro: MACRO(X,Y). (Vamos supor que há uma boa razão que você não pode usar uma função inline.) Você quer este macro para emular uma chamada para uma função com nenhum valor de retorno.


Exemplo 1: Isso deve funcionar como esperado

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

Exemplo 2: Este não deve resultar em um erro do compilador

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

Exemplo 3:. Isso deve não compilação

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

A maneira ingênua para escrever a macro é assim:

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

Esta é uma solução muito ruim que deixa todos os três exemplos, e eu não precisa explicar o porquê.

Ignore o que a macro realmente faz, isso não é o ponto.


Agora, a maneira que eu mais frequentemente ver macros escrita é colocá-los entre chaves, como este:

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

Isto resolve o exemplo 1, porque a macro está em um bloco de declaração. Mas o exemplo 2 é quebrado porque colocamos um ponto e vírgula após a chamada para a macro. Isso faz com que o compilador acha que o ponto e vírgula é uma declaração por si só, o que significa que a instrução else não corresponde a qualquer instrução if! E, por último, o exemplo 3 compila OK, embora não há nenhuma vírgula, porque um bloco de código não precisa de um ponto e vírgula.


Existe uma maneira de escrever uma macro para que ele passar todos os três exemplos?


Nota: Eu estou submetendo a minha própria resposta como parte do aceita, mas se alguém tem uma solução melhor sensação livre para postá-lo aqui, pode obter mais votos do que o meu método. :)

Foi útil?

Solução

Macros devem ser evitadas; preferem funções embutidas para eles em todos os momentos. Qualquer valor de compilador o seu sal deve ser capaz de inlining uma pequena função como se fosse uma macro, e uma função inline respeitará namespaces e outros âmbitos, bem como avaliar todos os argumentos uma vez.

Se ele deve ser uma macro, um loop while (já sugerido) vai funcionar, ou você pode tentar o operador vírgula:

#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 \
 )

O (void)0 faz com que a declaração para avaliar a uma de tipo void, eo uso de vírgulas em vez de ponto e vírgula permite que ele seja usado dentro de uma declaração, e não apenas como um autônomo. Eu ainda recomendaria uma função inline para uma série de razões, menos do que sendo escopo e o fato de que MACRO(a++, b++) incrementará a e b duas vezes.

Outras dicas

Não é uma solução bastante inteligente:

#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)

Agora você tem uma declaração de nível de bloco único, que deve ser seguido por um ponto e vírgula. Este se comporta como esperado e desejado em todos os três exemplos.

Eu sei que você disse "ignorar o que a macro faz", mas as pessoas vão encontrar esta questão através de pesquisa com base no título, então eu acho que a discussão de novas técnicas para funções imitar com os macros são garantidos.

O mais perto que eu conheço é:

#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)

Este faz o seguinte:

  • funciona corretamente em cada um dos contextos indicados.
  • Avalia cada um dos seus argumentos exatamente uma vez, que é uma característica garantida de uma chamada de função (assumindo que em ambos os casos há exceções em qualquer uma dessas expressões).
  • Actua sobre quaisquer tipos, pelo uso de "auto" do C ++ 0x. Isso ainda não é padrão C ++, mas não há outra maneira de obter as variáveis ??tmp exigidas pela regra da avaliação individual.
  • não requer o chamador ter nomes importados de std namespace, que a macro original faz, mas uma função não iria.

No entanto, ainda difere de uma função em que:

  • Em alguns usos inválidos pode dar erros do compilador diferentes ou avisos.
  • Vai errado se X ou Y conter usos de 'MACRO_tmp_1' ou 'MACRO_tmp_2' do âmbito envolvente.
  • Em relação à coisa namespace std: a função usa seu próprio contexto lexical para procurar nomes, enquanto uma macro usa o contexto de seu site de chamada. Não há nenhuma maneira de escrever uma macro que se comporta como uma função a este respeito.
  • não pode ser usado como a expressão de retorno de uma função de vazio, que uma expressão vazio (tais como a solução de vírgula) pode. Isto é ainda mais de um problema quando o tipo de retorno desejado não é vazio, especialmente quando usado como um lvalue. Mas a solução vírgula não pode incluir o uso de declarações, porque eles são declarações, então escolha um ou usar o ({...}) extensão GNU.

Aqui está uma resposta que vem desde o libc6! Dando uma olhada no /usr/include/x86_64-linux-gnu/bits/byteswap.h, descobri o truque que você procura.

Alguns críticos das soluções anteriores:

  • A solução da Kip não permite avaliando a uma expressão , que é, no final, muitas vezes necessária.
  • O coppro solução não permite atribuir uma variável como as expressões são separados, mas pode ser avaliada como uma expressão.
  • A solução de Steve Jessop usa a palavra-chave C ++ 11 auto, tudo bem, mas sinta-se livre para usar o tipo conhecido / esperado em seu lugar.

O truque é usar tanto a construção (expr,expr) e um escopo {}:

#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; \
    } \
  )

Observe o uso da palavra-chave register, é apenas uma dica para o compilador. Os parâmetros X e Y macro são (já) cercado em parênteses e Casted para um tipo esperado. Esta solução funciona corretamente com pré e pós-incremento como parâmetros são avaliados apenas uma vez.

Para o exemplo propósito, mesmo que não solicitado, eu adicionei a declaração __x + __y;, que é a maneira de fazer todo o bloco a ser avaliado como a expressão precisa.

É mais seguro usar void(); se você quer certificar-se a macro não será avaliada como uma expressão, sendo assim ilegal onde um rvalue é esperado.

No entanto , a solução é não ISO C ++ compatível como se queixam de g++ -pedantic:

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

A fim de dar algum descanso a g++, uso (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE) para que a nova definição é:

#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; \
    } \
  ))

A fim de melhorar a minha solução ainda um pouco mais, vamos usar a palavra-chave __typeof__, como visto em MIN e MAX em 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; \
    } \
  ))

Agora, o compilador irá determinar o tipo apropriado. Isso também é uma extensão gcc.

Observe a remoção da palavra-chave register, como seria o seguinte aviso quando utilizado com um tipo de classe:

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

C ++ 11 nos trouxe lambdas, que pode ser extremamente útil nesta situação:

#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))

Você manter o poder gerador de macros, mas têm um alcance confortável a partir da qual você pode retornar o que quiser (incluindo void). Além disso, a questão da avaliação de parâmetros macro várias vezes é evitado.

Crie um bloco usando

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

Não adicionar um; após o while (false)

A sua resposta sofre com o problema de múltiplos de avaliação, para que (por exemplo)

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

vai fazer algo inesperado e provavelmente indesejada.

Como já foi mencionado, você deve evitar macros sempre que possível. Eles são perigosos na presença de efeitos colaterais se os argumentos de macro são avaliados mais de uma vez. Se você souber o tipo de argumentos (ou pode usar C ++ 0x auto recurso), você poderia usar temporários para reforçar a avaliação individual.

Outro problema: a ordem em que múltiplas avaliações acontecer não pode ser o que você espera

Considere este código:

#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;
}

E é saída como compilado e executado na minha máquina:

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

Se você está disposto a adotar a prática de sempre usando chaves em sua if,

Seu macro seria simplesmente perder o último ponto e vírgula:

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

Exemplo 1: (compila)

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

Exemplo 2: (compila)

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

Exemplo 3: (não compila)

do_something();
MACRO(x, y)
do_something();
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top