Sempre perguntei isso, mas nunca recebi uma resposta realmente boa; Eu acho que quase qualquer programador antes mesmo de escrever o primeiro "Hello World" encontrou uma frase como "macro nunca deve ser usada", "macro são ruins" e assim por diante, minha pergunta é: por quê? Com o novo C ++ 11, existe uma alternativa real depois de tantos anos?
A parte fácil é sobre macros como #pragma
, que são específicas da plataforma e do compilador, e na maioria das vezes têm sérias falhas como #pragma once
essa propensa a erros em pelo menos 2 situações importantes: mesmo nome em caminhos diferentes e com algumas configurações de rede e sistemas de arquivos.
Mas, em geral, e quanto às macros e alternativas para seu uso?
c++
c++11
c-preprocessor
user1849534
fonte
fonte
#pragma
não é macro.#pragma
.constexpr
,inline
funções etemplates
, masboost.preprocessor
echaos
mostrar que as macros têm seu lugar. Sem mencionar as macros de configuração para compiladores, plataformas, etc.Respostas:
As macros são como qualquer outra ferramenta - um martelo usado em um assassinato não é mau porque é um martelo. É mau da maneira como a pessoa o usa. Se você quer martelar pregos, um martelo é a ferramenta perfeita.
Existem alguns aspectos das macros que as tornam "ruins" (expandirei cada uma delas posteriormente e sugerirei alternativas):
Então, vamos expandir um pouco aqui:
1) As macros não podem ser depuradas. Quando você tem uma macro que se traduz em um número ou string, o código-fonte terá o nome da macro, e muitos depuradores, você não pode "ver" para o que a macro se traduz. Então você realmente não sabe o que está acontecendo.
Substituição : Use
enum
ouconst T
Para macros "semelhantes a funções", porque o depurador trabalha em um nível "por linha de origem onde você estiver", sua macro funcionará como uma única instrução, não importa se é uma instrução ou cem. Torna difícil descobrir o que está acontecendo.
Substituição : use funções - inline se precisar ser "rápido" (mas cuidado, pois muito inline não é uma coisa boa)
2) Expansões macro podem ter efeitos colaterais estranhos.
O famoso é
#define SQUARE(x) ((x) * (x))
e o usox2 = SQUARE(x++)
. Isso leva ax2 = (x++) * (x++);
que, mesmo que fosse um código válido [1], quase certamente não seria o que o programador queria. Se fosse uma função, seria bom fazer x ++ e x só aumentaria uma vez.Outro exemplo é "if else" em macros, digamos que temos isto:
e depois
Na verdade, torna-se completamente errado ...
Substituição : funções reais.
3) Macros não têm namespace
Se tivermos uma macro:
e temos algum código em C ++ que usa begin:
Agora, que mensagem de erro você acha que recebeu e onde você procura um erro [presumindo que você tenha esquecido completamente - ou nem mesmo sabido - a macro inicial que reside em algum arquivo de cabeçalho que outra pessoa escreveu? [e ainda mais divertido se você incluir essa macro antes da inclusão - você estará se afogando em erros estranhos que não fazem absolutamente nenhum sentido quando você olha para o próprio código.
Substituição : Bem, não existe tanto uma substituição como uma "regra" - use apenas nomes em maiúsculas para macros e nunca use nomes em maiúsculas para outras coisas.
4) As macros têm efeitos que você não percebe
Faça esta função:
Agora, sem olhar para a macro, você pensaria que begin é uma função, que não deve afetar x.
Esse tipo de coisa, e já vi exemplos muito mais complexos, pode REALMENTE bagunçar o seu dia!
Substituição : não use uma macro para definir x ou passe x como um argumento.
Há momentos em que usar macros é definitivamente benéfico. Um exemplo é envolver uma função com macros para passar informações de arquivo / linha:
Agora podemos usar
my_debug_malloc
como o malloc regular no código, mas ele tem argumentos extras, então quando chegar ao final e verificarmos "quais elementos de memória não foram liberados", podemos imprimir onde a alocação foi feita para que o o programador pode rastrear o vazamento.[1] É um comportamento indefinido atualizar uma variável mais de uma vez "em um ponto de sequência". Um ponto de sequência não é exatamente o mesmo que uma declaração, mas para a maioria das intenções e propósitos, é como devemos considerá-lo. Isso fará com que
x++ * x++
seja atualizadox
duas vezes, o que é indefinido e provavelmente levará a valores diferentes em sistemas diferentes e também a valores de resultado diferentesx
.fonte
if else
problemas podem ser resolvidos envolvendo o corpo da macro dentrodo { ... } while(0)
. Este comporta-se como seria de esperar com relação aif
efor
e outras questões de controle de fluxo potencialmente arriscados. Mas sim, uma função real geralmente é uma solução melhor.#define macro(arg1) do { int x = func(arg1); func2(x0); } while(0)
note: expanded from macro 'begin'
e mostrarão ondebegin
está definido.O ditado "macros são ruins" geralmente se refere ao uso de #define, não #pragma.
Especificamente, a expressão se refere a estes dois casos:
definindo números mágicos como macros
usando macros para substituir expressões
Sim, para os itens na lista acima (números mágicos devem ser definidos com const / constexpr e as expressões devem ser definidas com funções [normal / inline / template / inline template].
Aqui estão alguns dos problemas introduzidos pela definição de números mágicos como macros e substituindo expressões por macros (em vez de definir funções para avaliar essas expressões):
ao definir macros para números mágicos, o compilador não retém informações de tipo para os valores definidos. Isso pode causar avisos de compilação (e erros) e confundir as pessoas ao depurar o código.
ao definir macros em vez de funções, os programadores que usam esse código esperam que funcionem como funções e não funcionam.
Considere este código:
Você esperaria que aec fosse 6 após a atribuição a c (como seria, usando std :: max em vez da macro). Em vez disso, o código executa:
Além disso, as macros não oferecem suporte a namespaces, o que significa que definir macros em seu código limitará o código do cliente em quais nomes eles podem usar.
Isso significa que se você definir a macro acima (para max), não será mais capaz de
#include <algorithm>
em qualquer um dos códigos abaixo, a menos que você escreva explicitamente:Ter macros em vez de variáveis / funções também significa que você não pode pegar seus endereços:
se uma macro como constante for avaliada como um número mágico, você não pode passá-la por endereço
para uma macro como função, você não pode usá-la como um predicado ou pegar o endereço da função ou tratá-la como um functor.
Edit: Por exemplo, a alternativa correta ao
#define max
anterior:Isso faz tudo o que a macro faz, com uma limitação: se os tipos de argumentos forem diferentes, a versão do modelo força você a ser explícito (o que na verdade leva a um código mais seguro e explícito):
Se este máximo for definido como uma macro, o código será compilado (com um aviso).
Se este máximo for definido como uma função de modelo, o compilador apontará a ambigüidade e você terá que dizer
max<int>(a, b)
oumax<double>(a, b)
(e assim declarar explicitamente sua intenção).fonte
const int someconstant = 437;
, e pode ser usado quase de todas as maneiras que uma macro seria usada. Da mesma forma para pequenas funções. Existem algumas coisas onde você pode escrever algo como uma macro que não funcionará em uma expressão regular em C (você pode fazer algo que calcule a média de um array de qualquer tipo de número, o que C não pode fazer - mas C ++ tem modelos por isso). Embora o C ++ 11 adicione mais algumas coisas que "você não precisa de macros para isso", a maioria já foi resolvida no C / C ++ anterior.max
emin
se eles forem seguidos por um parêntese esquerdo. Mas você não deve definir tais macros ...Um problema comum é este:
Irá imprimir 10, não 5, porque o pré-processador irá expandir desta forma:
Esta versão é mais segura:
fonte
DIV
macro pode ser reescrita com um par de () ao redorb
.#define DIV(a,b)
, não#define DIV (a,b)
, o que é muito diferente.#define DIV(a,b) (a) / (b)
não é bom o suficiente; como prática geral, sempre adicione colchetes externos, como este:#define DIV(a,b) ( (a) / (b) )
As macros são valiosas especialmente para a criação de código genérico (os parâmetros da macro podem ser qualquer coisa), às vezes com parâmetros.
Mais, este código é colocado (ou seja, inserido) no ponto em que a macro é usada.
OTOH, resultados semelhantes podem ser obtidos com:
funções sobrecarregadas (diferentes tipos de parâmetros)
modelos, em C ++ (tipos e valores de parâmetros genéricos)
funções embutidas (coloque o código onde são chamados, em vez de pular para uma definição de ponto único - no entanto, esta é uma recomendação para o compilador).
editar: quanto ao motivo pelo qual a macro é ruim:
1) sem verificação de tipo dos argumentos (eles não têm tipo), então podem ser facilmente mal utilizados 2) às vezes se expandem em um código muito complexo, que pode ser difícil de identificar e entender no arquivo pré-processado 3) é fácil cometer erros -prone código em macros, como:
e então ligar
que se expande em
2 + 3 * 4 + 5 (e não em: (2 + 3) * (4 + 5)).
Para ter o último, você deve definir:
fonte
Não acho que haja algo de errado em usar definições de pré-processador ou macros como você as chama.
Eles são um conceito de (meta) linguagem encontrado em c / c ++ e, como qualquer outra ferramenta, podem facilitar sua vida se você souber o que está fazendo. O problema com as macros é que elas são processadas antes do seu código c / c ++ e geram um novo código que pode estar com defeito e causar erros do compilador que são quase óbvios. Pelo lado positivo, eles podem ajudá-lo a manter seu código limpo e economizar muita digitação, se usado de maneira adequada, portanto, tudo se resume à preferência pessoal.
fonte
Macros em C / C ++ podem servir como uma ferramenta importante para controle de versão. O mesmo código pode ser entregue a dois clientes com uma configuração secundária de macros. Eu uso coisas como
Esse tipo de funcionalidade não é tão facilmente possível sem macros. Macros são, na verdade, uma ótima ferramenta de gerenciamento de configuração de software e não apenas uma forma de criar atalhos para reutilização de código. Definir funções com o propósito de reutilização em macros pode definitivamente criar problemas.
fonte
Acho que o problema é que as macros não são bem otimizadas pelo compilador e são "feias" para ler e depurar.
Freqüentemente, uma boa alternativa são funções genéricas e / ou funções inline.
fonte
Macros de pré-processador não são ruins quando são usadas para fins pretendidos, como:
Alternativas- Pode-se usar algum tipo de arquivo de configuração no formato ini, xml, json para fins semelhantes. Mas usá-los terá efeitos de tempo de execução no código que uma macro do pré-processador pode evitar.
fonte