Le pré-processeurCest à juste titre craint et rejeté par la communauté C++. Les fonctions, const et modèles intégrés 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 énumérer 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 ses commentaires.
En tant que wrappers pour les fonctions de débogage, pour transmettre automatiquement des éléments tels que __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 un code complet et 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, ceci est remplacé par la boucle basée sur la plage for .
Les gardes de fichiers d'en-tête nécessitent des macros.
Existe-t-il d'autres zones que les macros nécessitent? 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 prises 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 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 pervers, leur abus est pervers.
La plupart:
__LINE__
et __FILE__
)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 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é, il faudrait utiliser une sorte de classe de chaîne 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 code de flux de programme (return
, break
et continue
) dans une fonction, le comportement est différent du code réellement inséré 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.
L'évidence inclut les gardes
#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
Les frameworks de tests unitaires pour C++ tels que UnitTest ++ s'articulent autour des macros de préprocesseur. Quelques lignes de code de test unitaire se transforment en une hiérarchie de classes qu'il ne serait pas amusant de taper manuellement. Sans quelque chose comme UnitTest ++ et sa magie de préprocesseur, je ne sais pas comment écrire efficacement des tests unitaires pour C++.
Disons que nous allons ignorer des choses évidentes comme les gardes de tête.
Parfois, vous voulez générer du code qui doit être copié/collé par le précompilateur:
#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 produire des résultats encore meilleurs (c’est-à-dire générer automatiquement les valeurs côte à côte avec leurs noms de variables)
D'autres fois, vous aurez besoin du __FILE__ et/ou du __LINE__ de certains codes pour générer des informations de débogage, par exemple. Ce qui suit est 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 à 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, 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
(enfin, je n'ai vu que ce genre de code utilisé correctement une fois)
Dernier point, mais non le moindre, 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é à partir de la page d'accueil de boost)
Ce 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++.
Craindre le pré-processeur C, c'est comme craindre les ampoules à incandescence simplement parce que nous obtenons des ampoules fluorescentes. Oui, le premier peut être {électricité | temps de programmation} inefficace. Oui, vous pouvez être (littéralement) brûlé par eux. Mais ils peuvent faire le travail si vous le gérez correctement.
Lorsque vous programmez des systèmes intégrés, C utilise pour être la seule option en dehors de l'assembleur de formulaires. Après avoir programmé sur le bureau avec C++, puis basculé vers des cibles plus petites et intégrées, vous apprendrez à cesser de vous inquiéter des «lacunes» de tant de fonctions C nues (macros incluses) et à essayer de déterminer le meilleur usage sûr que vous pouvez en tirer. fonctionnalités.
Alexander Stepanov dit :
Lorsque nous programmons en C++, nous ne devrions pas avoir honte de son héritage C, mais bien faire pleine utilisation de celui-ci. Les seuls problèmes avec C++, et même les seuls problèmes avec C, apparaissent quand eux-mêmes ne sont pas compatibles avec leur propre logique.
Nous utilisons les macros __FILE__
et __LINE__
à des fins de diagnostic dans le lancement, la capture et la journalisation d'exceptions riches, ainsi que des analyseurs de fichiers journaux automatisés dans notre infrastructure d'assurance qualité.
Par exemple, une macro de projection 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 ça:
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 elle écrit également un message dans un fichier journal comprenant le nom du fichier et le numéro de 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 de code.
Jetez un œil à boost la bibliothèque de préprocesseurs , c'est une sorte de méta-méta-programmation. Dans topic-> motivation, vous pouvez trouver un bon exemple.
Certains éléments très avancés et utiles peuvent toujours être créés à l'aide de préprocesseurs (macros), ce que vous ne pourrez jamais utiliser à l'aide des "constructions de langage" c ++, y compris des modèles.
Exemples:
Faire quelque chose à la fois d'un identifiant C et d'une chaîne
Moyen facile d'utiliser des variables de types enum sous forme de chaîne en C
J'utilise parfois des macros pour pouvoir définir des informations à un endroit, mais 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 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"
Quelque chose comme
void debugAssert(bool val, const char* file, int lineNumber);
#define assert(x) debugAssert(x,__FILE__,__LINE__);
Pour que vous puissiez 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.
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 donne les outils et vous permet de décider quoi faire.
#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)];
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;\
};\
}\
Vous pouvez utiliser #defines pour vous aider avec des 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 fichier 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.
Les compilateurs peuvent refuser votre demande à 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 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 un comportement spécifique du compilateur/système d'exploitation/matériel.
Il vous permet de faire de votre interface 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
Il semble que VA_ARGS n'ait été mentionné qu'indirectement jusqu'à présent:
Lorsque vous écrivez du code C++ 03 générique 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 obtenons des modèles variés avec C++ 11, nous pouvons résoudre ce problème "correctement" avec un modèle.
Peut-être que l’utilisation optimale des macros est dans le développement indépendant de la plate-forme ..__Pensez aux cas d’incohérence de type - avec les macros, vous pouvez simplement utiliser différents fichiers d’en-tête - comme:
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
Beaucoup plus lisible que de le mettre en œuvre autrement, à mon avis.
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 arrivé 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, cela était un bon exemple 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 qui lui indiquerait le drapeau dont il avait besoin.
La macro définie comme:
#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 tenté d'effectuer, si vous ne l'aviez pas déjà. La raison pour laquelle deux d'entre eux était de retourner une valeur dans 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;
}
...
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 une autre macro 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 implémentations disponibles: Google "BOOST_FOREACH"
Bons articles disponibles: Amour conditionnel: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html
J'ai utilisé le pré-processeur pour calculer des nombres à virgule fixe à partir de valeurs à virgule flottante utilisées dans des systèmes intégrés qui ne peuvent pas utiliser de virgule flottante dans le code compilé. Il est pratique d’avoir toutes ses connaissances en unités du monde réel et de ne pas y penser en points fixes.
Exemple:
// TICKS_PER_UNIT is defined in floating point to allow the conversions to compute during compile-time.
#define TICKS_PER_UNIT 1024.0
// NOTE: The TICKS_PER_x_MS will produce constants in the preprocessor. The (long) cast will
// guarantee there are no floating point values in the embedded code and will produce a warning
// if the constant is larger than the data type being stored to.
// Adding 0.5 sec to the calculation forces rounding instead of truncation.
#define TICKS_PER_1_MS( ms ) (long)( ( ( ms * TICKS_PER_UNIT ) / 1000 ) + 0.5 )
Si vous avez une liste de champs qui sont utilisés pour un tas de choses, par exemple en définissant une structure, en sérialisant cette structure vers/à partir d'un format binaire, en insérant des bases de données, etc., vous pouvez (récursivement!) utiliser le pré-processeur pour éviter de répéter votre liste de champs.
Ceci est certes hideux. Mais peut-être parfois mieux que de mettre à jour une longue liste de champs à plusieurs endroits? J'ai utilisé cette technique exactement une fois, et c'était très utile une fois.
Bien sûr, la même idée générale est largement utilisée dans les langues avec une réflexion appropriée - il suffit d’instruire la classe et d’opérer successivement pour chaque domaine. Le faire dans le pré-processeur C est fragile, illisible et pas toujours portable. Je le mentionne donc avec une certaine appréhension. Néanmoins, le voici ...
(EDIT: Je vois maintenant que cela ressemble à ce que @Andrew Johnson a dit le 9/18; cependant, l’idée d’inclure récursivement le même fichier l’emmène un peu plus loin.)
// file foo.h, defines class Foo and various members on it without ever repeating the
// list of fields.
#if defined( FIELD_LIST )
// here's the actual list of fields in the class. If FIELD_LIST is defined, we're at
// the 3rd level of inclusion and somebody wants to actually use the field list. In order
// to do so, they will have defined the macros STRING and INT before including us.
STRING( fooString )
INT( barInt )
#else // defined( FIELD_LIST )
#if !defined(FOO_H)
#define FOO_H
#define DEFINE_STRUCT
// recursively include this same file to define class Foo
#include "foo.h"
#undef DEFINE_STRUCT
#define DEFINE_CLEAR
// recursively include this same file to define method Foo::clear
#include "foo.h"
#undef DEFINE_CLEAR
// etc ... many more interesting examples like serialization
#else // defined(FOO_H)
// from here on, we know that FOO_H was defined, in other words we're at the second level of
// recursive inclusion, and the file is being used to make some particular
// use of the field list, for example defining the class or a single method of it
#if defined( DEFINE_STRUCT )
#define STRING(a) std::string a;
#define INT(a) long a;
class Foo
{
public:
#define FIELD_LIST
// recursively include the same file (for the third time!) to get fields
// This is going to translate into:
// std::string fooString;
// int barInt;
#include "foo.h"
#endif
void clear();
};
#undef STRING
#undef INT
#endif // defined(DEFINE_STRUCT)
#if defined( DEFINE_ZERO )
#define STRING(a) a = "";
#define INT(a) a = 0;
#define FIELD_LIST
void Foo::clear()
{
// recursively include the same file (for the third time!) to get fields.
// This is going to translate into:
// fooString="";
// barInt=0;
#include "foo.h"
#undef STRING
#undef int
}
#endif // defined( DEFINE_ZERO )
// etc...
#endif // end else clause for defined( FOO_H )
#endif // end else clause for defined( FIELD_LIST )
Vous pouvez utiliser les constantes #define
sur la ligne de commande du compilateur à l'aide de l'option -D
ou /D
. Ceci 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.
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 activer la journalisation supplémentaire dans une version de débogage et la désactiver pour une version validée sans la surcharge d'une vérification booléenne. Donc, au lieu de:
void Log::trace(const char *pszMsg) {
if (!bDebugBuild) {
return;
}
// Do the logging
}
...
log.trace("Inside MyFunction");
Vous pouvez avoir:
#ifdef _DEBUG
#define LOG_TRACE log.trace
#else
#define LOG_TRACE void
#endif
...
LOG_TRACE("Inside MyFunction");
Lorsque _DEBUG n'est pas défini, cela ne générera aucun code. Votre programme s'exécutera plus rapidement et le texte pour la journalisation de la trace ne sera pas compilé dans votre exécutable.
Les macros sont utiles pour simuler la syntaxe des instructions switch:
switch(x) {
case val1: do_stuff(); break;
case val2: do_other_stuff();
case val3: yet_more_stuff();
default: something_else();
}
pour les types de valeur non intégrale. Dans cette question:
Utilisation de chaînes dans les instructions switch - Où en sommes-nous avec C++ 17?
vous trouverez des réponses suggérant certaines approches impliquant lambdas, mais malheureusement, ce sont les macros qui nous rapprochent le plus:
SWITCH(x)
CASE val1 do_stuff(); break;
CASE val2 do_other_stuff();
CASE val3 yet_more_stuff();
DEFAULT something_else();
END
#define COLUMNS(A,B) [(B) - (A) + 1]
struct
{
char firstName COLUMNS( 1, 30);
char lastName COLUMNS( 31, 60);
char address1 COLUMNS( 61, 90);
char address2 COLUMNS( 91, 120);
char city COLUMNS(121, 150);
};
Pouvez-vous implémenter cela comme une fonction inline?
#define my_free(x) do { free(x); x = NULL; } while (0)
Vous avez besoin de macros pour les identificateurs de ressources dans Visual Studio, car le compilateur de ressources ne les comprend que (autrement dit, cela ne fonctionne pas avec const ou enum).