Quand les macros C ++ sont-elles utiles?

Le préprocesseur C est à juste titre redouté et ignoré par la communauté C ++. Les fonctions intégrées, les consts et les modèles sont 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 énumérer les utilisations que vous trouvez pour les macros que vous ne pouvez pas faire sans le préprocesseur.

S’il vous plaît mettez chaque cas d’utilisation dans une réponse séparée afin qu’il puisse être voté et si vous savez comment obtenir l’une des réponses sans le préprocesseur, indiquez comment dans les commentaires de cette réponse.

En tant que wrappers pour les fonctions de débogage, pour passer automatiquement des choses comme __FILE__ , __LINE__ , etc.:

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

Les méthodes doivent toujours être complètes, 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 l'utiliser comme ça:

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

Depuis C ++ 11, elle est remplacée par la boucle basée sur la plage .

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

Y a-t-il d’autres zones nécessitant des macros? Pas beaucoup (le cas échéant).

Y a-t-il d’autres situations qui bénéficient de macros? OUI!!!

Un endroit où j’utilise des macros est avec un code très répétitif. Par exemple, lorsque vous encapsulez du 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 taper les blocs de capture complets à chaque fois, je tape simplement:

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

Cela facilite également la maintenance. Si jamais je dois append un nouveau type d’exception, il n’y a qu’un seul endroit pour l’append.

Il y a aussi d’autres exemples utiles: beaucoup d’entre eux incluent les macros de préprocesseur __FILE__ et __LINE__ .

De toute façon, les macros sont très utiles lorsqu’elles sont utilisées correctement. Les macros ne sont pas mauvaises – leur mauvaise utilisation est mauvaise.

Compilation conditionnelle interne, 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 

La plupart:

  1. Inclure les gardes
  2. Compilation conditionnelle
  3. Reporting (macros prédéfinies comme __LINE__ et __FILE__ )
  4. (rarement) dupliquer des modèles de code répétitifs.
  5. Dans le code de votre concurrent.

Lorsque vous souhaitez créer une chaîne à partir d’une expression, le meilleur exemple est assert ( #x 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 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 caractère const char * était utilisé, une sorte de classe de chaîne devrait être utilisée pour effectuer la concaténation à l’exécution:

 const char* BaseHkey = "Software\\Microsoft\\Internet Explorer\\"; RegOpenKey(HKEY_CURRENT_USER, (ssortingng(BaseHkey) + "Settings").c_str(), &settings); RegOpenKey(HKEY_CURRENT_USER, (ssortingng(BaseHkey) + "TypedURLs").c_str(), &URLs); 

Lorsque vous souhaitez modifier le code du stream de programme ( return , break et continue ) dans une fonction, il se comporte différemment du code qui est en réalité intégré 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 évidents comprennent les gardes

 #ifndef MYHEADER_H #define MYHEADER_H ... #endif 

Vous ne pouvez pas effectuer de court-circuit des 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 ignorerons des choses évidentes, comme les gardes-têtes.

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

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

ce 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 comme:

 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 donner de meilleurs résultats (par exemple, générer 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 pour 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 comme:

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

D'autres fois, vous devez générer du code en utilisant les opérateurs de concaténation # et ##, comme générer des getters et des setters pour une propriété (c'est le cas par exemple).

D'autres fois, vous allez générer du code que ne comstackra pas s'il est utilisé via une fonction, comme:

 #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 

(encore, je n'ai vu que ce genre de code correctement utilisé)

Enfin, le célèbre boost::foreach !!!

 #include  #include  #include  int main() { std::ssortingng hello( "Hello, world!" ); BOOST_FOREACH( char ch, hello ) { std::cout << ch; } return 0; } 

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

Ce qui est (IMHO) beaucoup mieux que std::for_each .

Les macros sont donc 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, ils restnt effectivement du code C jamais traduit en C ++ correct.

Les frameworks de tests unitaires pour C ++ comme UnitTest ++ tournent essentiellement autour des macros de préprocesseurs. Quelques lignes de code de test unitaire se développent en une hiérarchie de classes qui ne serait pas du tout amusante à taper manuellement. Sans quelque chose comme UnitTest ++ et sa magie de préprocesseur, je ne sais pas comment vous pourriez écrire efficacement des tests unitaires pour C ++.

Craindre le préprocesseur C, c’est comme craindre les ampoules à incandescence juste parce que nous avons des ampoules fluorescentes. Oui, le premier peut être {élecsortingcité | temps du programmeur} inefficace. Oui, vous pouvez vous faire littéralement brûler. Mais ils peuvent faire le travail si vous le manipulez correctement.

Lorsque vous programmez des systèmes embarqués, C est la seule option, à part l’assembleur de formulaires. Après avoir programmé sur ordinateur de bureau avec C ++ puis basculé vers des cibles embarquées plus petites, vous apprenez à ne plus vous soucier des “inélégances” de nombreuses fonctionnalités C (macros incluses) et à essayer de vous assurer une utilisation optimale. fonctionnalités.

Alexander Stepanov dit :

Lorsque nous programmons en C ++, nous ne devrions pas avoir honte de son inheritance C, mais en tirer pleinement parti. Les seuls problèmes avec C ++, et même les seuls problèmes avec C, surviennent lorsqu’ils ne sont pas eux-mêmes compatibles avec leur propre logique.

Nous utilisons les macros __FILE__ et __LINE__ à des fins de diagnostic dans le lancement, la capture et la journalisation des exceptions riches en informations, ainsi que dans les scanners de fichiers journaux automatisés de notre infrastructure QA.

Par exemple, une macro de lancement OUR_OWN_THROW peut être utilisée avec des parameters de type d’exception et de constructeur pour cette exception, y compris une description textuelle. Comme ça:

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

Bien entendu, cette macro jettera l’exception InvalidOperationException avec la description en tant que paramètre constructeur, mais elle inscrira également un message dans un fichier journal comprenant le nom du fichier et le numéro de la ligne où le jet s’est produit et sa description textuelle. L’exception renvoyée recevra un identifiant, qui sera également consigné. Si l’exception est interceptée quelque part ailleurs dans le code, elle sera marquée comme telle et le fichier journal indiquera que cette exception spécifique a été traité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.

Certains éléments très avancés et utiles peuvent toujours être construits en utilisant le préprocesseur (macros), ce que vous ne pourriez jamais faire en utilisant les “constructions de langage” c ++, y compris les modèles.

Exemples:

Faire quelque chose à la fois un identifiant C et une chaîne

Un moyen facile d’utiliser des variables de types enum sous forme de chaîne dans C

Métaprogrammation du préprocesseur

J’utilise occasionnellement des macros pour pouvoir définir des informations à un endroit, mais je les utilise de différentes manières dans différentes parties du code. C’est juste un peu mauvais 🙂

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 peut être défini pour utiliser 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" 

Répétition de code.

Jetez un coup d’oeil à la bibliothèque de préprocesseurs , c’est une sorte de méta-méta-programmation. Dans le sujet-> motivation, vous pouvez trouver un bon exemple.

Une utilisation courante est la détection de l’environnement de compilation. Pour le développement multiplate-forme, vous pouvez écrire un jeu de code pour Linux, par exemple, et un autre pour Windows quand aucune bibliothèque multi-plateforme n’existe déjà.

Ainsi, dans un exemple approximatif, un mutex multi-plateforme peut avoir

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

Pour les fonctions, elles sont utiles lorsque vous souhaitez ignorer explicitement le type de sécurité. 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 prendre en photo, mais le langage vous donne 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__); 

Alors que vous pouvez juste 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 assert 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 thread en cours, vous pouvez l’utiliser comme une expression constante:

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

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

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

Comstackz 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 tout en déboguant un problème (ou même le laisser pendant tout le cycle de développement), puis le désactiver quand le temps est venu.

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

Il vous permet de faire de votre interface des fonctionnalités spécifiques à Compstackr / 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 Comstackr/OS/HArdware configuration" #endif 

Dans mon dernier emploi, je travaillais sur un scanner de virus. Pour faciliter le débogage, je me suis retrouvé dans de nombreux cas, mais dans une application aussi exigeante, le coût d’un appel de fonction est trop élevé. Donc, je suis venu avec cette petite macro, qui me permettait toujours d’activer la journalisation de débogage sur une version de version sur un site client, sans que le coût d’un appel de fonction vérifie l’indicateur de débogage et revienne 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 de la VA_ARGS dans les fonctions de journal, cela était un bon exemple pour une macro comme celle-ci.

Avant cela, j’ai utilisé une macro dans une application haute sécurité qui devait indiquer à l’utilisateur qu’il ne disposait pas du bon access, et cela lui indiquerait quel drapeau il lui fallait.

La ou les macro (s) 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 vérifications sur l’interface utilisateur, et cela vous indiquerait quels rôles étaient autorisés à effectuer l’action que vous avez tenté d’effectuer, si vous n’aviez pas déjà ce rôle. La raison de deux d’entre eux était de renvoyer une valeur à certains endroits et de revenir d’une fonction 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; } ... 

Quoi qu’il en soit, c’est comme ça que je les ai utilisés, et je ne suis pas sûr de savoir comment cela aurait pu être aidé avec des modèles.

Encore une autre pour les macros. T: type, c: conteneur, 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) 

Utilisation (concept montrant, pas réel):

 void MultiplyEveryElementInList(std::list& ints, int mul) { foreach(std::list, ints, i) (*i) *= mul; } int GetSumOfList(const std::list& ints) { int ret = 0; foreach_const(std::list, ints, i) ret += *i; return ret; } 

Meilleures implémentations disponibles: Google “BOOST_FOREACH”

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

Peut-être que la grande utilisation des macros est en développement indépendant de la plate-forme. Pensez aux cas d’incohérence de type – avec les macros, vous pouvez simplement utiliser des fichiers d’en-tête différents – 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’implémenter autrement, à mon avis.

Semble que VA_ARGS n’a été mentionné que indirectement jusqu’à présent:

Lorsque vous écrivez du code C ++ 03 générique et que vous avez besoin d’un nombre variable de parameters (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 get_op_from_name hypothétique. C’est juste un exemple. Il pourrait y avoir un autre code générique entourant l’appel VA_ARGS.

Une fois que nous avons 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!" < 

Vous pouvez également définir une macro RELEASE_ONLY.

Vous pouvez #define constantes sur la ligne de commande du compilateur en utilisant l’option -D ou /D This is often useful when cross-compiling the same software for multiple platforms because you can have your makefiles control what constants are defined for each platform.