Quando as macros C ++ são benéficas? [fechadas]

177

o pré-processador C é justificadamente temido e evitado pela comunidade C ++. Funções alinhadas, consts e modelos são geralmente uma alternativa mais segura e superior a a #define.

A seguinte macro:

#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)  

não é de forma alguma superior ao tipo seguro:

inline bool succeeded(int hr) { return hr >= 0; }

Mas as macros têm seu lugar, liste os usos que você encontra para as macros que você não pode fazer sem o pré-processador.

Coloque cada caso de uso em uma resposta separada para que possa ser votado e, se você souber como obter uma das respostas sem o pré-professor, aponte como nos comentários dessa resposta.

Motti
fonte
Certa vez, peguei um aplicativo C ++ cheio de macros que levou 45 minutos para compilar, substitui as macros por funções embutidas e reduzi a compilação para menos de 15 minutos.
endian
Static Assert
Özgür
O tópico trata de contextos nos quais as macros são benéficas, não de contextos nos quais elas são subótimas.
underscore_d

Respostas:

123

Como wrappers para as funções de depuração, para passar automaticamente coisas como __FILE__, __LINE__, etc:

#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif
Frank Szczerba
fonte
14
Na verdade, o snippet original: << FILE ":" << está bom, FILE gera uma constante de string, que será concatenada com o ":" em uma única string pelo pré-processador.
24730 Frank Szczerba
12
Isso requer apenas o pré-processador, porque __FILE__e __LINE__ também exigem o pré-processador. Usá-los em seu código é como um vetor de infecção para o pré-processador.
TED
93

Os métodos sempre devem ser códigos completos e compiláveis; macros podem ser fragmentos de código. Assim, você pode definir uma macro foreach:

#define foreach(list, index) for(index = 0; index < list.size(); index++)

E use-o da seguinte maneira:

foreach(cookies, i)
    printf("Cookie: %s", cookies[i]);

Desde o C ++ 11, isso é substituído pelo loop for baseado em intervalo .

jdmichal
fonte
6
+1 Se você estiver usando alguma sintaxe ridiculamente complexa do iterador, escrever uma macro no estilo foreach pode facilitar muito a leitura e a manutenção do código. Eu fiz isso, funciona.
postfuturist
9
A maioria dos comentários é totalmente irrelevante a ponto de as macros poderem ser fragmentos de código em vez de código completo. Mas obrigado pela nitpicking.
jdmichal
12
Isso não é C ++. Se você estiver executando C ++, deverá usar iteradores e std :: for_each.
ChrisH
20
Eu discordo, chrish. Antes do lambda, for_eachera algo desagradável, porque o código em que cada elemento era executado não era local para o ponto de chamada. foreach, (e eu recomendo, em BOOST_FOREACHvez de uma solução manual), manter o código próximo ao site de iteração, tornando-o mais legível. Dito isto, uma vez lançada a lambda, for_eachpode ser o caminho a seguir.
GManNickG 18/08/09
8
E é interessante notar que BOOST_FOREACH é em si uma macro (mas muito bem pensado um)
Tyler McHenry
59

Os protetores de arquivo de cabeçalho precisam de macros.

Existem outras áreas que precisam de macros? Não são muitos (se houver).

Existem outras situações que se beneficiam de macros? SIM!!!

Um lugar que eu uso macros é com código muito repetitivo. Por exemplo, ao agrupar o código C ++ para ser usado com outras interfaces (.NET, COM, Python, etc ...), preciso capturar diferentes tipos de exceções. Aqui está como eu faço isso:

#define HANDLE_EXCEPTIONS \
catch (::mylib::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e); \
} \
catch (::std::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e, __LINE__, __FILE__); \
} \
catch (...) { \
    throw gcnew MyDotNetLib::UnknownException(__LINE__, __FILE__); \
}

Eu tenho que colocar essas capturas em todas as funções do invólucro. Em vez de digitar todos os blocos de captura completos, basta digitar:

void Foo()
{
    try {
        ::mylib::Foo()
    }
    HANDLE_EXCEPTIONS
}

Isso também facilita a manutenção. Se eu tiver que adicionar um novo tipo de exceção, há apenas um lugar em que preciso adicioná-lo.

Também existem outros exemplos úteis: muitos deles incluem as macros __FILE__e __LINE__pré - processador.

De qualquer forma, as macros são muito úteis quando usadas corretamente. Macros não são más - seu mau uso é mau.

Kevin
fonte
7
A maioria dos compiladores apoiar #pragma onceestes dias, por isso duvido guardas são realmente necessárias
1800 INFORMATION
13
Eles são, se você está escrevendo para todos os compiladores, em vez de só a maioria ;-)
Steve Jessop
30
Portanto, em vez da funcionalidade portátil pré-processador padrão, você recomenda usar uma extensão de pré-processador para evitar o uso do pré-processador? Parece meio ridículo para mim.
Logan Capaldo
#pragma oncequebra em muitos sistemas comuns de compilação.
Miles Rout
4
Há uma solução para isso que não requer macros: void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }. E no lado da função:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
MikeMB
51

Na maioria das vezes:

  1. Incluir guardas
  2. Compilação condicional
  3. Relatórios (macros predefinidas como __LINE__e __FILE__)
  4. (raramente) Duplicar padrões de código repetitivo.
  5. No código do seu concorrente.
David Thornley
fonte
Procurando alguma ajuda sobre como obter o número 5. Você pode me orientar para uma solução?
Max
50

Compilação condicional interna, para superar problemas de diferenças entre compiladores:

#ifdef ARE_WE_ON_WIN32
#define close(parm1)            _close (parm1)
#define rmdir(parm1)            _rmdir (parm1)
#define mkdir(parm1, parm2)     _mkdir (parm1)
#define access(parm1, parm2)    _access(parm1, parm2)
#define create(parm1, parm2)    _creat (parm1, parm2)
#define unlink(parm1)           _unlink(parm1)
#endif
Andrew Stein
fonte
12
Em C ++, o mesmo pode ser obtido através do uso de funções inline: <code> #ifdef ARE_WE_ON_WIN32 <br> inline int close (int i) {return _close (i); } #endif </code>
paercebal
2
Isso remove os # define, mas não os #ifdef e #endif. De qualquer forma, eu concordo com você.
Gorpik 23/10/08
19
NUNCA defina macros em minúsculas. Macros para alterar funções são o meu pesadelo (obrigado Microsoft). O melhor exemplo está na primeira linha. Muitas bibliotecas têm closefunções ou métodos. Então, quando você incluir o cabeçalho desta biblioteca e o cabeçalho nessa macro, você terá um grande problema, não poderá usar a API da biblioteca.
Marek R
AndrewStein, você vê algum benefício no uso de macros nesse contexto em relação à sugestão de @ paercebal? Caso contrário, parece que as macros são realmente gratuitas.
Einpoklum 20/04
1
#ifdef WE_ARE_ON_WIN32plz :)
Lightness Races em órbita
38

Quando você deseja transformar uma string em uma expressão, o melhor exemplo disso é assert( #xtransforma o valor de xem uma string).

#define ASSERT_THROW(condition) \
if (!(condition)) \
     throw std::exception(#condition " is false");
Motti
fonte
5
Apenas um nitpick, mas eu pessoalmente deixaria o ponto e vírgula desligado.
Michael Myers
10
Concordo que, na verdade, eu colocaria isso em um {do} while (false) (para evitar outros ataques altos), mas eu queria mantê-lo simples.
Motti
33

Às vezes, as constantes de string são melhor definidas como macros, pois você pode fazer mais com literais de string do que com a const char *.

por exemplo, literais de string podem ser facilmente concatenados .

#define BASE_HKEY "Software\\Microsoft\\Internet Explorer\\"
// Now we can concat with other literals
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "Settings", &settings);
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "TypedURLs", &URLs);

Se a const char *fosse usado, algum tipo de classe de cadeia teria que ser usada para executar a concatenação no tempo de execução:

const char* BaseHkey = "Software\\Microsoft\\Internet Explorer\\";
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "Settings").c_str(), &settings);
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "TypedURLs").c_str(), &URLs);
Motti
fonte
2
No C ++ 11, consideraria essa a parte mais importante (exceto incluir guardas). Macros são realmente a melhor coisa que temos para o processamento de strings em tempo de compilação. Essa é uma característica que eu espero que nós tenhamos em C ++ 11 ++
David Stone
1
Essa é a situação que me levou a desejar macros em C #.
Rawling
2
Eu gostaria de poder +42 isso. Um aspecto muito importante, embora nem sempre lembrado, dos literais de strings.
Daniel Kamil Kozar 25/03
24

Quando você deseja alterar o código de fluxo do programa ( return, breake continue) em uma função se comporta de maneira diferente do código realmente embutido na função.

#define ASSERT_RETURN(condition, ret_val) \
if (!(condition)) { \
    assert(false && #condition); \
    return ret_val; }

// should really be in a do { } while(false) but that's another discussion.
Motti
fonte
Lançar uma exceção me parece uma alternativa melhor.
Einpoklum 20/04
Ao escrever extensões python C (++), as exceções são propagadas definindo uma sequência de exceções e retornando -1ou NULL. Portanto, uma macro pode reduzir bastante o código padrão aqui.
black_puppydog
20

O óbvio inclui guardas

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif
Kena
fonte
17

Você não pode executar um curto-circuito nos argumentos de chamada de função usando uma chamada de função regular. Por exemplo:

#define andm(a, b) (a) && (b)

bool andf(bool a, bool b) { return a && b; }

andm(x, y) // short circuits the operator so if x is false, y would not be evaluated
andf(x, y) // y will always be evaluated
1800 INFORMAÇÃO
fonte
3
Talvez um ponto mais geral: as funções avaliam seus argumentos exatamente uma vez. Macros podem avaliar argumentos mais ou menos vezes.
Steve Jessop
@ [Greg Rogers] tudo o que o pré-processador de macro faz é substituir o texto. Depois de entender isso, não deve haver mais mistério sobre isso.
1800 INFORMAÇÃO
Você pode obter o comportamento equivalente modelando andf em vez de forçar a avaliação a boolear antes de chamar a função. Eu não teria percebido o que você disse ser verdade sem tentar por mim mesmo. Interessante.
Greg Rogers
Como exatamente você poderia fazer isso com um modelo?
1800 INFORMAÇÃO
6
Ocultar operações de curto-circuito atrás de uma macro de estilo de função é uma das coisas que realmente não quero ver no código de produção.
MikeMB
17

Digamos que ignoraremos coisas óbvias, como guardas de cabeçalho.

Às vezes, você deseja gerar código que precisa ser copiado / colado pelo pré-compilador:

#define RAISE_ERROR_STL(p_strMessage)                                          \
do                                                                             \
{                                                                              \
   try                                                                         \
   {                                                                           \
      std::tstringstream strBuffer ;                                           \
      strBuffer << p_strMessage ;                                              \
      strMessage = strBuffer.str() ;                                           \
      raiseSomeAlert(__FILE__, __FUNCSIG__, __LINE__, strBuffer.str().c_str()) \
   }                                                                           \
   catch(...){}                                                                \
   {                                                                           \
   }                                                                           \
}                                                                              \
while(false)

que permite codificar isso:

RAISE_ERROR_STL("Hello... The following values " << i << " and " << j << " are wrong") ;

E pode gerar mensagens como:

Error Raised:
====================================
File : MyFile.cpp, line 225
Function : MyFunction(int, double)
Message : "Hello... The following values 23 and 12 are wrong"

Observe que a mistura de modelos com macros pode levar a resultados ainda melhores (ou seja, gerar automaticamente os valores lado a lado com seus nomes de variáveis)

Outras vezes, você precisa do __FILE__ e / ou __LINE__ de algum código, para gerar informações de depuração, por exemplo. A seguir, é um clássico do Visual C ++:

#define WRNG_PRIVATE_STR2(z) #z
#define WRNG_PRIVATE_STR1(x) WRNG_PRIVATE_STR2(x)
#define WRNG __FILE__ "("WRNG_PRIVATE_STR1(__LINE__)") : ------------ : "

Como no código a seguir:

#pragma message(WRNG "Hello World")

gera mensagens como:

C:\my_project\my_cpp_file.cpp (225) : ------------ Hello World

Outras vezes, você precisa gerar código usando os operadores de concatenação # e ##, como gerar getters e setters para uma propriedade (isso é para casos bastante limitados).

Outras vezes, você irá gerar código que não será compilado se usado por meio de uma função, como:

#define MY_TRY      try{
#define MY_CATCH    } catch(...) {
#define MY_END_TRY  }

Qual pode ser usado como

MY_TRY
   doSomethingDangerous() ;
MY_CATCH
   tryToRecoverEvenWithoutMeaningfullInfo() ;
   damnThoseMacros() ;
MY_END_TRY

(ainda assim, eu só vi esse tipo de código usado corretamente uma vez )

Por último, mas não menos importante, o famoso boost::foreach!!!

#include <string>
#include <iostream>
#include <boost/foreach.hpp>

int main()
{
    std::string hello( "Hello, world!" );

    BOOST_FOREACH( char ch, hello )
    {
        std::cout << ch;
    }

    return 0;
}

(Nota: cópia de código / colada na página inicial do impulso)

Qual é (IMHO) muito melhor do que std::for_each.

Portanto, as macros são sempre úteis porque estão fora das regras normais do compilador. Mas acho que na maioria das vezes vejo um, eles são efetivamente restos do código C nunca convertidos em C ++ apropriado.

paercebal
fonte
1
Use o CPP apenas para o que o compilador não pode fazer. Por exemplo, RAISE_ERROR_STL deve usar o CPP apenas para determinar arquivo, linha e assinatura de função e passá-los para uma função (possivelmente inline) que faz o resto.
Rainer Blome
Atualize sua resposta para refletir o C ++ 11 e o endereço do comentário de @ RainerBlome.
Einpoklum 20/04
@RainerBlome: Nós concordamos. A macro RAISE_ERROR_STL é anterior ao C ++ 11, portanto, nesse contexto, é totalmente justificada. Meu entendimento (mas nunca tive a oportunidade de lidar com esses recursos específicos) é que você pode usar modelos variados (ou macros?) No C ++ moderno para resolver o problema de maneira mais elegante.
paercebal
@einpoklum: "Atualize sua resposta para refletir o C ++ 11 e abordar o comentário de RainerBlome" No. :-). . . Acredito que, na melhor das hipóteses, adicionarei uma seção para o C ++ moderno, com implementações alternativas que reduzem ou eliminam a necessidade de macros, mas o ponto permanece: as macros são feias e más, mas quando você precisa fazer algo que o compilador não entende , você faz isso através de macros.
paercebal
Mesmo com o C ++ 11, muito do que sua macro faz pode ser deixado para uma função: #include <sstream> #include <iostream> using namespace std; void trace(char const * file, int line, ostream & o) { cerr<<file<<":"<<line<<": "<< static_cast<ostringstream & >(o).str().c_str()<<endl; } struct Oss { ostringstream s; ostringstream & lval() { return s; } }; #define TRACE(ostreamstuff) trace(__FILE__, __LINE__, Oss().lval()<<ostreamstuff) int main() { TRACE("Hello " << 123); return 0; }Dessa forma, a macro é muito menor.
Rainer Blome
16

As estruturas de teste de unidade para C ++, como o UnitTest ++, giram em torno das macros do pré-processador. Algumas linhas de código de teste de unidade se expandem para uma hierarquia de classes que não seria divertido digitar manualmente. Sem algo como o UnitTest ++ e a mágica do pré-processador, não sei como você escreveria testes de unidade para C ++ com eficiência.

Joe
fonte
Unittests são perfeitamente possíveis de escrever sem uma estrutura. No final, depende realmente do tipo de saída que você deseja. Se você não se importa, um simples valor de saída indicando sucesso ou falha deve estar perfeitamente correto.
Clearer
15

Temer o pré-processador C é como temer as lâmpadas incandescentes apenas porque temos lâmpadas fluorescentes. Sim, o primeiro pode ser {eletricidade | tempo do programador} ineficiente. Sim, você pode se queimar (literalmente) por eles. Mas eles podem fazer o trabalho se você lidar com isso adequadamente.

Quando você programa sistemas incorporados, C costuma ser a única opção além do montador de formulários. Depois de programar na área de trabalho com C ++ e depois mudar para destinos menores e incorporados, você aprende a parar de se preocupar com “deselegâncias” de tantos recursos simples de C (incluindo macros) e apenas tentando descobrir o melhor e seguro uso que você pode obter daqueles recursos.

Alexander Stepanov diz :

Quando programamos em C ++, não devemos ter vergonha de sua herança C, mas fazer pleno uso dela. Os únicos problemas com C ++, e mesmo os únicos problemas com C, surgem quando eles próprios não são consistentes com sua própria lógica.

VictorH
fonte
Eu acho que essa é a atitude errada. Só porque você pode aprender a "lidar com isso adequadamente" não significa que vale a pena o tempo e o esforço de alguém.
Neil G
9

Usamos as macros __FILE__e __LINE__para fins de diagnóstico em lançamentos, capturas e registros de exceções ricas em informações, juntamente com scanners de arquivos de log automatizados em nossa infraestrutura de controle de qualidade.

Por exemplo, uma macro de lançamento OUR_OWN_THROWpode ser usada com parâmetros de tipo e construtor de exceção para essa exceção, incluindo uma descrição textual. Como isso:

OUR_OWN_THROW(InvalidOperationException, (L"Uninitialized foo!"));

Obviamente, essa macro lançará a InvalidOperationExceptionexceção com a descrição como parâmetro construtor, mas também gravará uma mensagem em um arquivo de log que consiste no nome do arquivo e no número da linha em que a ocorrência ocorreu e sua descrição textual. A exceção lançada receberá um ID, que também será registrado. Se a exceção for capturada em algum outro lugar do código, ela será marcada como tal e o arquivo de log indicará que essa exceção específica foi tratada e, portanto, não é provável que seja a causa de qualquer falha que possa ser registrada posteriormente. Exceções não tratadas podem ser facilmente detectadas por nossa infraestrutura automatizada de controle de qualidade.

Johann Gerell
fonte
9

Repetição de código.

Dê uma olhada para aumentar a biblioteca de pré-processadores , é um tipo de meta-meta-programação. No tópico motivação, você pode encontrar um bom exemplo.

Ruggero Turra
fonte
Quase todos, se não todos, casos - a repetição de código pode ser evitada com chamadas de função.
Einpoklum 20/04
@einpoklum: Eu não concordo. Dê uma olhada no link
Ruggero Turra
9

Alguns itens muito avançados e úteis ainda podem ser criados usando pré-processador (macros), o que você nunca seria capaz de fazer usando as "construções de linguagem" do c ++, incluindo modelos.

Exemplos:

Fazendo algo tanto um identificador C quanto uma string

Maneira fácil de usar variáveis ​​de tipos de enumeração como string em C

Aumentar a metaprogramação do pré-processador

Suma
fonte
O terceiro elo é quebrado fyi
Robin Hartland
Dê uma olhada stdio.he sal.harquive vc12para entender melhor.
Elshan 17/05
7

Ocasionalmente, uso macros para definir informações em um único local, mas de diferentes maneiras em diferentes partes do código. É apenas um pouco mal :)

Por exemplo, em "field_list.h":

/*
 * List of fields, names and values.
 */
FIELD(EXAMPLE1, "first example", 10)
FIELD(EXAMPLE2, "second example", 96)
FIELD(ANOTHER, "more stuff", 32)
...
#undef FIELD

Em seguida, para uma enumeração pública, pode ser definido apenas para usar o nome:

#define FIELD(name, desc, value) FIELD_ ## name,

typedef field_ {

#include "field_list.h"

    FIELD_MAX

} field_en;

E em uma função init privada, todos os campos podem ser usados ​​para preencher uma tabela com os dados:

#define FIELD(name, desc, value) \
    table[FIELD_ ## name].desc = desc; \
    table[FIELD_ ## name].value = value;

#include "field_list.h"
Andrew Johnson
fonte
1
Nota: técnica semelhante pode ser implementada mesmo sem uma inclusão separada. Veja: stackoverflow.com/questions/147267/… stackoverflow.com/questions/126277/…
Suma
6

Um uso comum é para detectar o ambiente de compilação; para o desenvolvimento de plataforma cruzada, você pode escrever um conjunto de códigos para o Linux, por exemplo, e outro para o Windows, quando ainda não existe uma biblioteca de plataforma cruzada para seus propósitos.

Portanto, em um exemplo aproximado, um mutex de plataforma cruzada pode ter

void lock()
{
    #ifdef WIN32
    EnterCriticalSection(...)
    #endif
    #ifdef POSIX
    pthread_mutex_lock(...)
    #endif
}

Para funções, elas são úteis quando você deseja ignorar explicitamente a segurança de tipo. Como os muitos exemplos acima e abaixo para fazer ASSERT. Obviamente, como muitos recursos do C / C ++, você pode dar um tiro no pé, mas a linguagem fornece as ferramentas e permite que você decida o que fazer.

Doug T.
fonte
Desde que o questionador perguntou: isso pode ser feito sem macros, incluindo cabeçalhos diferentes através de diferentes caminhos de inclusão por plataforma. Estou inclinado a concordar que as macros geralmente são mais convenientes.
Steve Jessop
Eu concordo. Se você começar a usar macros para o efeito, o código pode rapidamente tornar-se muito menos legível
Nemanja Trifunovic
6

Algo como

void debugAssert(bool val, const char* file, int lineNumber);
#define assert(x) debugAssert(x,__FILE__,__LINE__);

Para que você possa, por exemplo, ter

assert(n == true);

e obtenha o nome do arquivo de origem e o número da linha do problema impressos em seu log, se n for falso.

Se você usar uma chamada de função normal, como

void assert(bool val);

em vez da macro, tudo o que você pode obter é o número da linha da sua função assert impressa no log, o que seria menos útil.

Keshi
fonte
Por que você iria reinventar a roda quando implementações da biblioteca padrão já fornecer através <cassert>da assert()macro, que despeja o arquivo / linha / info função? (em todas as implementações que já vi)
underscore_d
4
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

Diferente da solução de modelo 'preferencial' discutida em um encadeamento atual, você pode usá-lo como uma expressão constante:

char src[23];
int dest[ARRAY_SIZE(src)];
fizzer
fonte
2
Isso pode ser feito com modelos em uma maneira mais segura (o que não irá compilar se passou um ponteiro em vez de uma matriz) stackoverflow.com/questions/720077/calculating-size-of-an-array/...
Motti
1
Agora que temos constexpr no C ++ 11, a versão segura (não macro) também pode ser usada em uma expressão constante. template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
David Stone
3

Você pode usar #defines para ajudar nos cenários de depuração e teste de unidade. Por exemplo, crie variantes de log especiais das funções de memória e crie um memlog_preinclude.h especial:

#define malloc memlog_malloc
#define calloc memlog calloc
#define free memlog_free

Compile seu código usando:

gcc -Imemlog_preinclude.h ...

Um link no seu memlog.o para a imagem final. Agora você controla malloc, etc., talvez para fins de registro ou para simular falhas de alocação para testes de unidade.

Andrew Johnson
fonte
3

Quando você está tomando uma decisão em tempo de compilação sobre o comportamento específico do Compiler / OS / Hardware.

Ele permite que você faça sua interface com os recursos específicos do Comppiler / OS / Hardware.

#if defined(MY_OS1) && defined(MY_HARDWARE1)
#define   MY_ACTION(a,b,c)      doSothing_OS1HW1(a,b,c);}
#elif define(MY_OS1) && defined(MY_HARDWARE2)
#define   MY_ACTION(a,b,c)      doSomthing_OS1HW2(a,b,c);}
#elif define(MY_SUPER_OS)
          /* On this hardware it is a null operation */
#define   MY_ACTION(a,b,c)
#else
#error  "PLEASE DEFINE MY_ACTION() for this Compiler/OS/HArdware configuration"
#endif
Loki Astari
fonte
3

Eu uso macros para definir facilmente exceções:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

onde DEF_EXCEPTION é

#define DEF_EXCEPTION(A, B) class A : public exception\
  {\
  public:\
    virtual const char* what() const throw()\
    {\
      return B;\
    };\
  }\
MrBeast
fonte
2

Os compiladores podem recusar sua solicitação para incorporar.

As macros sempre terão seu lugar.

Algo que acho útil é #define DEBUG para rastreamento de depuração - você pode deixá-lo 1 durante a depuração de um problema (ou até deixá-lo ligado durante todo o ciclo de desenvolvimento) e depois desativá-lo na hora de enviar.

pesado
fonte
10
Se o compilador recusar sua solicitação para incorporar, pode haver um motivo muito bom. Um bom compilador será melhor em alinhar corretamente do que você e um ruim oferecerá mais problemas de desempenho do que isso.
David Thornley
@DavidThornley Ou talvez não seja um ótimo compilador de otimização, como GCC ou CLANG / LLVM. Alguns compiladores são apenas uma porcaria.
Roteamento de milhas
2

No meu último trabalho, eu estava trabalhando em um antivírus. Para facilitar a depuração, eu tinha muitos logs presos em todo o lugar, mas em um aplicativo de alta demanda como esse, a despesa de uma chamada de função é muito cara. Então, eu criei essa pequena macro, que ainda me permitia ativar o log de depuração em uma versão de lançamento no site de clientes, sem o custo de uma chamada de função verificaria o sinalizador de depuração e retornaria sem registrar nada, ou se ativado , faria o log ... A macro foi definida da seguinte maneira:

#define dbgmsg(_FORMAT, ...)  if((debugmsg_flag  & 0x00000001) || (debugmsg_flag & 0x80000000))     { log_dbgmsg(_FORMAT, __VA_ARGS__);  }

Devido ao VA_ARGS nas funções de log, esse era um bom caso para uma macro como essa.

Antes disso, eu usava uma macro em um aplicativo de alta segurança que precisava dizer ao usuário que eles não tinham o acesso correto e que tipo de sinalizador eles precisavam.

As macro (s) definidas como:

#define SECURITY_CHECK(lRequiredSecRoles) if(!DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, true)) return
#define SECURITY_CHECK_QUIET(lRequiredSecRoles) (DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, false))

Em seguida, poderíamos simplesmente espalhar as verificações por toda a interface do usuário, e ele informaria quais funções tinham permissão para executar a ação que você tentou executar, se você ainda não tivesse essa função. A razão para dois deles foi retornar um valor em alguns lugares e retornar de uma função nula em outros ...

SECURITY_CHECK(ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR);

LRESULT CAddPerson1::OnWizardNext() 
{
   if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_EMPLOYEE) {
      SECURITY_CHECK(ROLE_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD ) -1;
   } else if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_CONTINGENT) {
      SECURITY_CHECK(ROLE_CONTINGENT_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR) -1;
   }
...

De qualquer forma, foi assim que os usei e não tenho certeza de como isso poderia ter sido ajudado com modelos ... Fora isso, tento evitá-los, a menos que seja realmente necessário.

LarryF
fonte
2

Mais uma macros foreach. T: tipo, c: contêiner, i: iterador

#define foreach(T, c, i) for(T::iterator i=(c).begin(); i!=(c).end(); ++i)
#define foreach_const(T, c, i) for(T::const_iterator i=(c).begin(); i!=(c).end(); ++i)

Uso (exibição de conceito, não real):

void MultiplyEveryElementInList(std::list<int>& ints, int mul)
{
    foreach(std::list<int>, ints, i)
        (*i) *= mul;
}

int GetSumOfList(const std::list<int>& ints)
{
    int ret = 0;
    foreach_const(std::list<int>, ints, i)
        ret += *i;
    return ret;
}

Melhores implementações disponíveis: Google "BOOST_FOREACH"

Bons artigos disponíveis: Amor condicional: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html

Não está na lista
fonte
2

Talvez o ótimo uso das macros esteja no desenvolvimento independente da plataforma. Pense em casos de inconsistência de tipo - com macros, você pode simplesmente usar diferentes arquivos de cabeçalho - como: --WIN_TYPES.H

typedef ...some struct

--POSIX_TYPES.h

typedef ...some another struct

--program.h

#ifdef WIN32
#define TYPES_H "WINTYPES.H"
#else 
#define TYPES_H "POSIX_TYPES.H"
#endif

#include TYPES_H

Muito legível do que implementá-lo de outras maneiras, na minha opinião.

rkellerm
fonte
2

Parece que VA_ARGS só foi mencionado indiretamente até agora:

Ao escrever código C ++ 03 genérico e você precisa de um número variável de parâmetros (genéricos), pode usar uma macro em vez de um modelo.

#define CALL_RETURN_WRAPPER(FnType, FName, ...)          \
  if( FnType theFunction = get_op_from_name(FName) ) {   \
    return theFunction(__VA_ARGS__);                     \
  } else {                                               \
    throw invalid_function_name(FName);                  \
  }                                                      \
/**/

Nota: Em geral, o nome check / throw também pode ser incorporado na hipóteseget_op_from_name função . Este é apenas um exemplo. Pode haver outro código genérico ao redor da chamada VA_ARGS.

Depois de obtermos modelos variados com o C ++ 11, podemos resolver isso "corretamente" com um modelo.

Martin Ba
fonte
1

Eu acho que esse truque é um uso inteligente do pré-processador que não pode ser emulado com uma função:

#define COMMENT COMMENT_SLASH(/)
#define COMMENT_SLASH(s) /##s

#if defined _DEBUG
#define DEBUG_ONLY
#else
#define DEBUG_ONLY COMMENT
#endif

Então você pode usá-lo assim:

cout <<"Hello, World!" <<endl;
DEBUG_ONLY cout <<"This is outputed only in debug mode" <<endl;

Você também pode definir uma macro RELEASE_ONLY.

Mathieu Pagé
fonte
2
Este truque não funciona de acordo com o padrão. Ele tenta criar um marcador de comentário através do pré-processador, mas os comentários devem ser removidos antes da execução do pré-processador. Um compilador em conformidade causará um erro de sintaxe aqui.
David Thornley
2
Desculpe David, mas o compilador deve conter uma segunda cópia da remoção do comentário.
13289 Joshua
muito mais fácil é tornar o sinalizador de depuração um bool const global e usar código como este: if (debug) cout << "..."; - não há necessidade de macros!
Stefan Monov
@ Stefan: De fato, é o que faço agora. Qualquer compilador decente não gerará nenhum código se a depuração for falsa nesse caso.
Mathieu Pagé
1

Você pode #defineconstantes na linha de comando do compilador usando a opção -Dou /DIsso geralmente é útil na compilação cruzada do mesmo software para várias plataformas, porque você pode fazer com que seus makefiles controlem quais constantes são definidas para cada plataforma.

bk1e
fonte