Question

Le pré-processeur C est à juste titre craint et rejeté par la communauté C ++. Les fonctions, les const et les modèles en ligne constituent généralement une alternative plus sûre et supérieure à un #define .

La macro suivante:

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

n'est en aucun cas supérieur au type safe:

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

Mais les macros ont leur place, veuillez répertorier les utilisations que vous trouvez pour les macros que vous ne pouvez pas utiliser sans le pré-processeur.

Veuillez mettre chaque cas d'utilisation dans une réponse distincte afin qu'il puisse être voté et si vous savez comment obtenir l'une des réponses sans que le préprocesseur indique comment, dans les commentaires de cette réponse.

Était-ce utile?

La solution

En tant que wrappers pour les fonctions de débogage, pour transmettre automatiquement des éléments tels que __ FICHIER __ , __ LIGNE __ , etc.:

#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif

Autres conseils

Les méthodes doivent toujours être complètes, du code compilable; les macros peuvent être des fragments de code. Ainsi, vous pouvez définir une macro foreach:

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

Et utilisez-le comme suit:

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

Depuis C ++ 11, cette boucle for basée sur la plage .

Les gardes de fichiers d'en-tête nécessitent des macros.

Existe-t-il d'autres domaines nécessitant des macros ? Pas beaucoup (le cas échéant).

Existe-t-il d'autres situations qui bénéficient des macros? OUI !!!

Un endroit où j'utilise des macros est avec du code très répétitif. Par exemple, lors du wrapping de code C ++ à utiliser avec d'autres interfaces (.NET, COM, Python, etc.), je dois intercepter différents types d'exceptions. Voici comment je fais ça:

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

Je dois mettre ces captures dans chaque fonction d'emballage. Plutôt que de saisir chaque fois le bloc de capture complet, je tape simplement:

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

Cela facilite également la maintenance. Si jamais je dois ajouter un nouveau type d'exception, il n'y a qu'un seul endroit où je dois l'ajouter.

Il existe également d'autres exemples utiles: beaucoup d'entre eux incluent les macros de préprocesseur __ FILE __ et __ LINE __ .

Quoi qu'il en soit, les macros sont très utiles lorsqu'elles sont utilisées correctement. Les macros ne sont pas diaboliques: leur usage abusif est diabolique.

Principalement:

  1. Inclure les gardes
  2. Compilation conditionnelle
  3. Reporting (macros prédéfinies telles que __ LINE __ et __ FILE __ )
  4. (rarement) Duplication de modèles de code répétitifs.
  5. Dans le code de votre concurrent.

Dans la compilation conditionnelle, pour surmonter les problèmes de différences entre les compilateurs:

#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

Lorsque vous souhaitez créer une chaîne à partir d'une expression, le meilleur exemple est assert ( #x transforme la valeur de x en chaîne).

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

Les constantes de chaîne sont parfois mieux définies en tant que macros car vous pouvez en faire plus avec des littéraux de chaîne qu'avec un const char * .

par exemple. Les littéraux de chaîne peuvent être facilement concaténés .

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

Si un const char * était utilisé, une sorte de classe de chaîne devrait être utilisée pour effectuer la concaténation au moment de l'exécution:

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

Lorsque vous souhaitez modifier le flux du programme ( return , pause et continuer ), le code d'une fonction se comporte différemment du code réellement utilisé. en ligne dans la fonction.

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

Les gardes sont une évidence

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif

Vous ne pouvez pas court-circuiter les arguments d'appel de fonction à l'aide d'un appel de fonction normal. Par exemple:

#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

Disons que nous allons ignorer des choses évidentes comme les gardes d'en-tête.

Parfois, vous souhaitez générer du code qui doit être copié / collé par le précompileur:

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

qui vous permet de coder ceci:

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

Et peut générer des messages tels que:

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

Notez que le mélange de modèles avec des macros peut conduire à des résultats encore meilleurs (en générant automatiquement les valeurs côte à côte avec leurs noms de variables)

D'autres fois, vous avez besoin du __FILE__ et / ou du __LINE__ de certains codes pour générer des informations de débogage, par exemple. Voici un classique de Visual C ++:

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

Comme avec le code suivant:

#pragma message(WRNG "Hello World")

il génère des messages tels que:

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

D'autres fois, vous devez générer du code à l'aide des opérateurs de concaténation # et ##, comme pour générer des getters et des setters pour une propriété (dans des cas assez limités, via).

D'autres fois, vous générerez du code qui ne sera pas compilé s'il est utilisé via une fonction, telle que:

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

Qui peut être utilisé comme

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

(néanmoins, je n'ai vu que ce type de code correctement utilisé une fois )

Enfin, le célèbre 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;
}

(Remarque: code copié / collé depuis la page d'accueil de boost)

Qui est (IMHO) bien meilleur que std :: for_each .

Ainsi, les macros sont toujours utiles car elles sont en dehors des règles normales du compilateur. Mais je trouve que la plupart du temps, j'en vois un, ce sont en réalité des restes de code C jamais traduits en C ++.

Nous utilisons les macros __ FILE __ et __ LINE __ à des fins de diagnostic lors de la génération, de la capture et de la journalisation d'exceptions riches, ainsi que des analyseurs de fichiers journaux automatisés dans notre infrastructure d'assurance qualité.

Par exemple, une macro OUR_OWN_THROW peut être utilisée avec des paramètres de type et de constructeur d'exception pour cette exception, y compris une description textuelle. Comme ceci:

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

Cette macro lève bien sûr l’exception InvalidOperationException avec la description en tant que paramètre constructeur, mais écrit également un message dans un fichier journal comprenant le nom du fichier et le numéro de la ligne où la projection a eu lieu. et sa description textuelle. L'exception levée aura un identifiant, qui sera également enregistré. Si l'exception est déjà interceptée ailleurs dans le code, elle sera marquée en tant que telle et le fichier journal indiquera alors que cette exception spécifique a été gérée et qu'il n'est donc probablement pas la cause d'un incident susceptible d'être consigné ultérieurement. Les exceptions non gérées peuvent être facilement détectées par notre infrastructure d'assurance qualité automatisée.

Répétition du code.

Consultez la page de la bibliothèque de préprocesseurs de boost , c'est une sorte de méta-méta-programmation. Dans la rubrique motivation, vous pouvez trouver un bon exemple.

Certains éléments très avancés et utiles peuvent toujours être générés à l'aide de préprocesseurs (macros), ce que vous ne pourrez jamais utiliser à l'aide des "constructions de langage" c ++. modèles inclus.

Exemples:

Créer quelque chose à la fois d'un identifiant C et d'une chaîne

Moyen facile de utiliser des variables de types enum en tant que chaîne en C

Métaprogrammation du préprocesseur Boost

J'utilise parfois des macros afin de pouvoir définir des informations à un endroit donné, mais de les utiliser de différentes manières dans différentes parties du code. C'est seulement un peu mal:)

Par exemple, dans "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

Ensuite, pour une énumération publique, il est possible de définir simplement le nom:

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

typedef field_ {

#include "field_list.h"

    FIELD_MAX

} field_en;

Et dans une fonction init privée, tous les champs peuvent être utilisés pour remplir une table avec les données:

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

#include "field_list.h"

Une utilisation courante consiste à détecter l’environnement de compilation. Pour le développement multiplateforme, vous pouvez écrire un ensemble de code pour Linux, par exemple, et un autre pour Windows, lorsqu'aucune bibliothèque multiplateforme n’existe déjà pour vos besoins.

Ainsi, dans un exemple approximatif, un mutex multiplateforme peut avoir

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

Pour les fonctions, elles sont utiles lorsque vous souhaitez explicitement ignorer le type safety. Tels que les nombreux exemples ci-dessus et ci-dessous pour faire ASSERT. Bien sûr, comme beaucoup de fonctionnalités C / C ++, vous pouvez vous tirer une balle dans le pied, mais le langage vous fournit les outils et vous permet de décider quoi faire.

Quelque chose comme

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

Pour que vous puissiez, par exemple, avoir

assert(n == true);

et obtenez le nom du fichier source et le numéro de ligne du problème imprimé dans votre journal si n est faux.

Si vous utilisez un appel de fonction normal tel que

void assert(bool val);

Au lieu de la macro, tout ce que vous pouvez obtenir est le numéro de ligne de votre fonction d'assertion imprimé dans le journal, ce qui serait moins utile.

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

Contrairement à la solution de modèle "préférée" décrite dans un fil de discussion actuel, vous pouvez l'utiliser comme expression constante:

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

Vous pouvez utiliser #defines pour vous aider dans les scénarios de débogage et de tests unitaires. Par exemple, créez des variantes de journalisation spéciales des fonctions de la mémoire et créez un memlog_preinclude.h spécial:

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

Compilez votre code en utilisant:

gcc -Imemlog_preinclude.h ...

Un lien dans votre memlog.o vers l'image finale. Vous contrôlez maintenant malloc, etc., peut-être à des fins de journalisation ou pour simuler des échecs d'allocation pour les tests unitaires.

J'utilise des macros pour définir facilement des exceptions:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

où DEF_EXCEPTION est

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

Les compilateurs peuvent refuser votre demande d'inline.

Les macros auront toujours leur place.

Quelque chose que je trouve utile est #define DEBUG pour le traçage du débogage - vous pouvez le laisser 1 pendant le débogage d'un problème (ou même le laisser actif pendant tout le cycle de développement), puis l'éteindre au moment de l'envoi.

Lorsque vous prenez une décision au moment de la compilation sur le comportement spécifique du compilateur / système d'exploitation / matériel.

Il vous permet de créer votre interface avec des fonctionnalités spécifiques à 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

Dans mon dernier emploi, je travaillais sur un scanner de virus. Pour faciliter les choses pour le débogage, beaucoup de journaux étaient bloqués, mais dans une application très demandée comme celle-là, le coût d'un appel de fonction est trop coûteux. Donc, je suis venu avec cette petite macro, qui me permettait toujours d'activer la journalisation du débogage sur une version publiée sur le site d'un client, sans le coût d'un appel de fonction vérifierait l'indicateur de débogage et reviendrait sans rien enregistrer, ou si activé , ferait la journalisation ... La macro a été définie comme suit:

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

En raison des VA_ARGS dans les fonctions de journalisation, ce fut un bon cas pour une macro comme celle-ci.

Avant cela, j’utilisais une macro dans une application de haute sécurité qui devait indiquer à l’utilisateur qu’il n’avait pas le bon accès et lui indiquer l’indicateur dont il avait besoin.

La ou les macros définies comme suit:

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

Ensuite, nous pourrions simplement saupoudrer les contrôles sur toute l'interface utilisateur, et le système vous indiquerait les rôles autorisés à effectuer l'action que vous avez essayé d'effectuer, si vous ne l'aviez pas déjà. La raison pour laquelle deux d’entre eux était de retourner une valeur à certains endroits et de revenir d’une fonction de vide dans d’autres ...

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

En tout cas, c'est comme ça que je les utilise, et je ne sais pas comment cela aurait pu être aidé avec des modèles ... Sinon, j'essaie de les éviter, sauf si VRAIMENT nécessaire.

Encore un autre macros foreach. T: type, c: conteneur, i: itérateur

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

Utilisation (concept montrant, pas réel):

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

Meilleures mises en œuvre disponibles: Google "BOOST_FOREACH"

.

Bons articles disponibles: Amour conditionnel: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html

Peut-être que l’utilisation optimale des macros réside dans un développement indépendant de la plateforme. Pensez aux cas d'incohérence de type - avec les macros, vous pouvez simplement utiliser différents fichiers d'en-tête - comme: --WIN_TYPES.H

typedef ...some struct

- POSIX_TYPES.h

typedef ...some another struct

- programme.h

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

#include TYPES_H

Beaucoup plus lisible que de l'appliquer autrement, à mon avis.

Il semble que VA_ARGS n'ait été mentionné que indirectement jusqu'à présent:

Lorsque vous écrivez du code générique C ++ 03 et que vous avez besoin d'un nombre variable de paramètres (génériques), vous pouvez utiliser une macro au lieu d'un modèle.

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

Remarque: En général, le nom check / throw peut également être intégré à la fonction hypothétique get_op_from_name . C'est juste un exemple. Il peut y avoir un autre code générique entourant l'appel VA_ARGS.

Une fois que nous avons obtenu des modèles variadiques avec C ++ 11, nous pouvons résoudre ce problème "correctement". avec un modèle.

Je pense que cette astuce est une utilisation intelligente du préprocesseur qui ne peut pas être émulé avec une fonction:

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

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

Ensuite, vous pouvez l'utiliser comme ceci:

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

Vous pouvez également définir une macro RELEASE_ONLY.

Vous pouvez # définir les constantes sur la ligne de commande du compilateur à l'aide de l'option -D ou / D . Cela est souvent utile lors de la compilation croisée du même logiciel pour plusieurs plates-formes car vous pouvez laisser vos makefiles contrôler les constantes définies pour chaque plate-forme.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top