Segredo das macros C++: por que do-while(0) e if-else são necessários em macros e 3 erros fatais

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Macros em C++: beleza perigosa ou mal necessário?

\n\n

Todo mundo que já programou em C++ já se deparou com macros. Por um lado, é uma ferramenta poderosa para metaprogramação que pode reduzir a quantidade de código repetitivo. Por outro, as macros são conhecidas por sua natureza traiçoeira. Basta pular um ponto e vírgula ou colocar os parênteses no lugar errado, e o código se transforma em um monstro imprevisível.

\n\n

Mas existe uma categoria especial de macros em C++ que parece francamente estranha. Você já viu um código assim?

\n\n
#define SAFE_DELETE(ptr) do { delete (ptr); (ptr) = nullptr; } while(0)
\n\n

Parece lógico: deletar o ponteiro e defini-lo como nulo. Mas por que esse do-while(0) sem sentido? Um loop que é garantido de executar exatamente uma vez? Parece redundância beirando o absurdo. Ou então este outro exemplo com if-else:

\n\n
#define LOG_ERROR(msg) if (g_logging) std::cerr << (msg)
\n\n

Parece simples. Mas é exatamente aqui que estão as armadilhas nas quais milhares de desenvolvedores já tropeçaram. Esta pergunta no Stack Overflow acumulou dezenas de milhares de visualizações, e hoje vamos entender por que essas construções não são um bug, mas uma feature, e quais erros de macros C++ elas previnem.

\n\n

Por que macros não são funções? O principal problema

\n\n

Antes de mergulharmos nos detalhes do do-while(0), vamos lembrar a diferença entre uma macro e uma função comum. Em C++, uma macro é uma substituição textual. O pré-processador simplesmente copia o corpo da macro para o local onde ela é chamada. Sem verificação de tipos, sem escopos — é uma substituição pura de strings.

\n\n

É por isso que surgem os clássicos erros em macros C++. Vejamos um exemplo simples:

\n\n
#define SQUARE(x) x * x\n\nint main() {\n    int a = 5;\n    int result = SQUARE(a + 1); // Esperamos 36, obtemos 11\n    std::cout << result; // Exibirá 11\n    return 0;\n}
\n\n

O que aconteceu? A macro se expandiu para a + 1 * a + 1. Devido à precedência de operadores, a multiplicação foi executada antes da adição. Primeiro erro — falta de parênteses. Vamos corrigir:

\n\n
#define SQUARE(x) ((x) * (x))
\n\n

Agora funciona. Mas isso é só o começo. Os problemas reais começam quando a macro contém múltiplas instruções ou construções de controle.

\n\n

Idioma do-while(0): escudo contra catástrofes sintáticas

\n\n

Voltemos ao nosso SAFE_DELETE. Por que não escrever a macro de forma mais simples, sem esse loop estranho?

\n\n
#define SAFE_DELETE(ptr) delete (ptr); (ptr) = nullptr;
\n\n

Parece lógico. Mas imagine usá-la neste contexto:

\n\n
if (ptr != nullptr)\n    SAFE_DELETE(ptr);\nelse\n    std::cout << "Pointer is null";
\n\n

Após a substituição, o pré-processador transformará isso em:

\n\n
if (ptr != nullptr)\n    delete (ptr);\n    (ptr) = nullptr;\nelse\n    std::cout << "Pointer is null";
\n\n

E aí está o bug! O else agora não pertence ao if, porque uma segunda instrução apareceu entre eles. O compilador emitirá um erro, mas a ideia é clara: a macro "quebrou" a construção de controle. É exatamente para esses casos que o do-while(0) é necessário. Ele transforma várias instruções em uma única composta, preservando a lógica:

\n\n
if (ptr != nullptr)\n    do { delete (ptr); (ptr) = nullptr; } while(0);\nelse\n    std::cout << "Pointer is null";
\n\n

Agora tudo funciona corretamente. O idioma de macros do-while(0) é uma técnica padrão que garante que a macro se comporte como uma única instrução.

\n\n

Por que exatamente do-while(0) e não apenas um bloco { }?

\n\n

Boa pergunta! Poderíamos simplesmente usar chaves:

\n\n
#define SAFE_DELETE(ptr) { delete (ptr); (ptr) = nullptr; }
\n\n

Vamos tentar usar

Blogs

Book Recommendations