Pergunta

O C pré-processador é justamente temido e evitado pela comunidade C ++. In-alinhados funções, consts e modelos são geralmente uma alternativa mais segura e superior a um #define.

A macro a seguir:

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

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

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

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

Por favor, coloque cada um casos de uso em uma resposta separada para que ele possa ser votado para cima e se você souber de como conseguir uma das respostas sem o ponto preprosessor para fora como em comentários que da resposta.

Foi útil?

Solução

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

Outras dicas

Os métodos devem sempre ser completo código, compilable; 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 usá-lo como assim:

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

Como C ++ 11, este é substituído por o baseado no intervalo para o laço .

guardas arquivo de cabeçalho necessitam de macros.

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

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

Um lugar que eu utilizar macros é com código muito repetitivo. Por exemplo, quando o invólucro de código C ++ para ser utilizado com outras interfaces (NET, COM, Python, etc ...), preciso pegar diferentes tipos de excepções. Aqui está como eu fazer 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 cada função wrapper. Em vez de digitar os blocos catch completos de cada vez, eu só digitar:

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

Isso também torna a manutenção mais fácil. Se eu tiver que adicionar um novo tipo de exceção, só há um lugar que eu preciso adicioná-lo.

Existem outros exemplos úteis também: muitos dos quais incluem os __FILE__ e __LINE__ macros de pré-processamento

.

De qualquer forma, as macros são muito úteis quando usados ??corretamente. Macros não são maus -. Sua uso indevido é mau

Na maior parte:

  1. Incluir guardas
  2. Conditional compilação
  3. Relatórios (macros pré-definidas como __LINE__ e __FILE__)
  4. (raramente) Duplicação de padrões de código repetitivos.
  5. No código do seu concorrente.

Dentro de compilação condicional, para superar as questões 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

Quando você quiser fazer uma seqüência fora de uma expressão, o melhor exemplo para isso é assert (#x transforma o valor de x a uma string).

#define ASSERT_THROW(condition) \
if (!(condition)) \
     throw std::exception(#condition " is false");

As constantes da corda às vezes são melhor definidas como macros desde que você pode fazer mais com strings literais do que com um const char *.

por exemplo. strings literais pode ser facilmente concatenado .

#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 um const char * foram utilizados, em seguida, algum tipo de classe string teria que ser usado para executar a concatenação em 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);

Quando você quiser mudar o fluxo do programa (return, break e continue) código em uma função se comporta de forma diferente do código que é realmente inlined 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.

O óbvio incluem guardas

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif

Você não pode executar um curto-circuito de 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

Vamos dizer que vamos ignorar as coisas óbvias como guardas de cabeçalho.

Às vezes, você quer gerar o código que precisa ser copiar / colar 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 que você código o seguinte:

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"

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

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

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

Tal como acontece com o seguinte código:

#pragma message(WRNG "Hello World")

gera mensagens como:

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

Outras vezes, você precisa gerar o código usando o # e ## operadores de concatenação, como gerar getters e setters para uma propriedade (Isto é para um casos bastante limitado, por meio).

Outras vezes, você vai gerar código que não irá compilar se usado através de uma função, como:

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

O que pode ser usado como

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

(ainda, 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 código / colado a partir do impulso homepage)

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

Assim, as macros são sempre úteis, porque eles estão fora das regras normais do compilador. Mas eu acho que a maioria do tempo eu ver um, eles são permanece eficaz de código C não traduzido para o adequado C ++.

estruturas de teste de unidade para C ++ como UnitTest ++ praticamente giram macros em torno de pré-processamento. Algumas linhas de código de teste de unidade expandir-se para uma hierarquia de classes que não seriam divertimento a todos para digitar manualmente. Sem algo como UnitTest ++ e é magia pré-processador, eu não sei como você se testes de unidade eficiente gravação para C ++.

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

Quando você programar sistemas embarcados, C usa para ser a única opção para além forma assembler. Após a programação na área de trabalho com C ++ e, em seguida, mudar para menores metas, embutidos, você aprende a parar de se preocupar “inelegancies” de tantos recursos C nuas (macros incluídas) e apenas tentando descobrir a melhor e seguro uso que você pode começar a partir daqueles recursos.

Alexander Stepanov diz :

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

Nós usamos as macros __FILE__ e __LINE__ para fins de diagnóstico em informação rica jogando exceção, captar e registrar, em conjunto com scanners de arquivo de log automatizados em nossa infra-estrutura de controle de qualidade.

Por exemplo, um OUR_OWN_THROW macro jogando pode ser usado com parâmetros de tipo de exceção e construtor para essa exceção, incluindo uma descrição textual. Como esta:

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

Esta vontade macro, claro, lançar a exceção InvalidOperationException com a descrição como parâmetro de construtor, mas ele também vai escrever uma mensagem para um arquivo de log que consiste no nome do arquivo e número da linha onde o lançamento ocorreu e sua descrição textual. A exceção lançada receberá um ID, que também é registrado. Se a exceção é travado nunca em outro lugar no código, ele será marcado como tal e o arquivo de log, então, indicam que esta excepção específica tem sido tratado e que, portanto, não é provável que a causa de qualquer acidente que possa ser registrada mais tarde. exceções não tratadas podem ser facilmente captado por nossa infra-estrutura de controle de qualidade automatizado.

repetição de código.

Tenha um olhar para biblioteca de impulso pré-processador , é uma espécie de meta-meta-programação. Em topic-> motivação que você pode encontrar um bom exemplo.

Algumas coisas muito avançado e útil ainda pode ser construído usando pré-processador (macros), que você nunca seria capaz de fazer usando o c ++ "construções de linguagem", incluindo modelos.

Exemplos:

Fazer algo ao mesmo tempo um identificador C e uma corda

Fácil maneira de usar variáveis ??de tipos enum como string em C

impulsionar Preprocessor Metaprogramação

Eu ocasionalmente usam macros para que eu possa definir as informações em um único lugar, mas usá-lo de maneiras diferentes 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 para usar apenas 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 pode ser usado 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"

Um uso comum é para detectar o ambiente de compilação, para o desenvolvimento multi-plataforma, você pode escrever um conjunto de código para Linux, por exemplo, e outra para janelas quando nenhuma biblioteca multiplataforma já existe para seus propósitos.

Assim, em um exemplo áspero um mutex multi-plataforma pode ter

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

Para funções, eles são úteis quando você deseja ignorar explicitamente a segurança de tipos. Tais como os muitos exemplos acima e abaixo para fazer ASSERT. Claro que, como um monte de C / C ++ as características que você pode atirar no próprio pé, mas a linguagem dá-lhe as ferramentas e permite que você decida o que fazer.

Algo como

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

Assim que você pode apenas por exemplo, têm

assert(n == true);

e obter o nome do arquivo fonte e número da linha do problema impresso para o seu log se n é falsa.

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 impresso para o log, o que seria menos útil.

#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

Ao contrário da solução de modelo 'preferido' discutido em uma thread atual, você pode usá-lo como uma expressão constante:

char src[23];
int dest[ARRAY_SIZE(src)];

Você pode usar # define para ajudar com a depuração e cenários de teste de unidade. Por exemplo, criar o registo especial variantes das funções de memória e criar um memlog_preinclude.h especial:

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

Compilar seu código usando:

gcc -Imemlog_preinclude.h ...

Um link em seu memlog.o à imagem final. agora você controlar malloc, etc, talvez para fins de registro, ou falhas de alocação de simulados para testes de unidade.

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

Compiladores pode recusar o seu pedido para em linha.

Macros sempre terá seu lugar.

Algo que eu encontro útil é DEBUG # define para o rastreamento de depuração - você pode deixá-lo 1 durante a depuração um problema (ou até mesmo deixá-lo ligado durante todo o ciclo de desenvolvimento), em seguida, desligá-lo quando é hora de navio

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

Ele permite que você faça o seu interface para recursos / OS / Hardware específicas Comppiler.

#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

No meu último emprego, eu estava trabalhando em um scanner de vírus. Para tornar a coisa mais fácil para mim depuração, eu tinha muita exploração madeireira preso em todo o lugar, mas em um aplicativo de alta demanda como essa, à custa de uma chamada de função é muito caro. Então, eu vim com este pequeno Macro, que ainda me permitiu habilitar o log de depuração em uma versão em um local de clientes, sem o custo de uma chamada de função iria verificar o sinalizador de depuração e apenas retornar sem registro de qualquer coisa, ou se estiver ativado , faria o registro ... a macro foi definida da seguinte forma:

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

Por causa dos VA_ARGS nas funções de log, este foi um bom caso para uma macro como esta.

Antes disso, eu usei uma macro em uma aplicação de alta segurança que precisava dizer ao usuário que não tem o acesso correto, e seria dizer-lhes que a bandeira que eles precisavam.

A macro (s) definido como:

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

Então, nós poderíamos apenas polvilhe os cheques em todo o interface do usuário, e ele iria dizer-lhe que os papéis foram autorizados a realizar a ação que você tentou fazer, se você já não tem esse papel. A razão para dois deles foi para retornar um valor em alguns lugares, e retornar de uma função vazio 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, é assim que eu usei-los, e eu não sei como isso poderia ter sido ajudado com modelos ... outras que, eu tento evitá-los, a menos que realmente necessário.

No entanto, mais foreach macros. T: tipo, c: recipiente, i: iterator

#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 (conceito que mostra, 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;
}

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

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

Talvez o uso greates de macros está em desenvolvimento independente de plataforma. Pense em casos do tipo inconsistência - 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, a minha opinião.

Parece VA_ARGS só foram mencionados indiretamente até agora:

Ao escrever C ++ genérico 03 código, e você precisa de um número variável de parâmetros (genéricos), você 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 de verificação / jogar também poderia ser incorporada a função get_op_from_name hipotética. Este é apenas um exemplo. Pode haver outros códigos genéricos em torno da chamada VA_ARGS.

Uma vez que temos modelos variádicos com C ++ 11, podemos resolver isso "corretamente" com um modelo.

Eu acho que este 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

Em seguida, você pode usá-lo como este:

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

Você também pode definir uma macro RELEASE_ONLY.

Você pode #define constantes na linha de comando do compilador usando a opção -D ou /D. Isto é frequentemente útil quando cross-compilar o mesmo software para múltiplas plataformas, porque você pode ter o seu controle makefiles que constantes são definidas para cada plataforma.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top