Quand les macros C ++ sont-elles utiles? [fermé]

177

Le préprocesseur C est à juste titre craint et rejeté 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 lister les utilisations que vous trouvez des macros que vous ne pouvez pas faire 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 le préprosesseur, indiquez comment dans les commentaires de cette réponse.

Motti
la source
Une fois, j'ai pris une application C ++ pleine de macros qui prenait 45 minutes à construire, j'ai remplacé les macros par des fonctions en ligne et j'ai réduit la construction à moins de 15 minutes.
endian
Static Assert
Özgür
Le fil de discussion concerne les contextes dans lesquels les macros sont bénéfiques, et non les contextes dans lesquels elles ne sont pas optimales.
underscore_d

Réponses:

123

Encapsulent les fonctions de débogage, de 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
Frank Szczerba
la source
14
En fait, l'extrait de code d'origine: << FILE ":" << convient, FILE génère une constante de chaîne, qui sera concaténée avec le ":" en une seule chaîne par le pré-processeur.
Frank Szczerba
12
Cela nécessite uniquement le préprocesseur car __FILE__et nécessite __LINE__ également le préprocesseur. Les utiliser dans votre code est comme un vecteur d'infection pour le préprocesseur.
TED
93

Les méthodes doivent toujours être du 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 ceci:

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

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

jdmichal
la source
6
+1 Si vous utilisez une syntaxe d'itérateur ridiculement complexe, l'écriture d'une macro de style foreach peut rendre votre code beaucoup plus facile à lire et à maintenir. Je l'ai fait, ça marche.
postfuturist
9
La plupart des commentaires sont totalement hors de propos au point que les macros peuvent être des fragments de code au lieu d'un code complet. Mais merci pour le pinaillage.
jdmichal
12
Ce n'est pas C ++. Si vous utilisez C ++, vous devriez utiliser des itérateurs et std :: for_each.
chrish
20
Je ne suis pas d'accord, chrish. Avant les lambda, for_eachc'était une mauvaise chose, car le code par lequel chaque élément était exécuté n'était pas local au point d'appel. foreach, (et je recommande vivement BOOST_FOREACHau lieu d'une solution roulée à la main) vous permet de garder le code près du site d'itération, le rendant plus lisible. Cela dit, une fois que le déploiement de lambda sera déployé, for_eachpourrait encore être la voie à suivre.
GManNickG
8
Et il convient de noter que BOOST_FOREACH est en soi une macro (mais très bien pensée)
Tyler McHenry
59

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

Y a-t-il d'autres domaines qui nécessitent des macros? Pas beaucoup (le cas échéant).

Y a-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 de l'encapsulation de code C ++ à utiliser avec d'autres interfaces (.NET, COM, Python, etc ...), je dois attraper 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 de wrapper. 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 ajouter un nouveau type d'exception, je n'ai besoin que d'un seul endroit pour l'ajouter.

Il existe également d'autres exemples utiles: dont 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 mauvaises - leur mauvaise utilisation est mauvaise.

Kevin
la source
7
La plupart des compilateurs prennent en charge #pragma onceces jours-ci, donc je doute que les gardes soient vraiment nécessaires
1800 INFORMATION
13
Ils le sont si vous écrivez pour tous les compilateurs au lieu de la plupart ;-)
Steve Jessop
30
Donc, au lieu d'une fonctionnalité de préprocesseur standard et portable, vous recommandez d'utiliser une extension de préprocesseur pour éviter d'utiliser le préprocesseur? Cela me semble ridicule.
Logan Capaldo
#pragma oncecasse sur de nombreux systèmes de construction courants.
Miles Rout
4
Il existe une solution pour ce qui ne nécessite pas de macros: void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }. Et du côté des fonctions:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
MikeMB
51

La plupart:

  1. Inclure les gardes
  2. Compilation conditionnelle
  3. Rapports (macros prédéfinies comme __LINE__et __FILE__)
  4. (rarement) Duplication de modèles de code répétitifs.
  5. Dans le code de votre concurrent.
David Thornley
la source
Vous cherchez de l'aide pour réaliser le numéro 5. Pouvez-vous me guider vers une solution?
Max
50

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
Andrew Stein
la source
12
En C ++, la même chose pourrait être obtenue grâce à l'utilisation de fonctions en ligne: <code> #ifdef ARE_WE_ON_WIN32 <br> inline int close (int i) {return _close (i); } <br> #endif </code>
paercebal
2
Cela supprime les # define, mais pas les #ifdef et #endif. Quoi qu'il en soit, je suis d'accord avec vous.
Gorpik
19
NE JAMAIS définir de macros minuscules. Les macros pour modifier les fonctions sont mon cauchemar (merci Microsoft). Le meilleur exemple est en première ligne. De nombreuses bibliothèques ont des closefonctions ou des méthodes. Ensuite, lorsque vous incluez l'en-tête de cette bibliothèque et l'en-tête avec cette macro que vous avez un gros problème, vous ne pouvez pas utiliser l'API de bibliothèque.
Marek R
AndrewStein, voyez-vous un avantage à l'utilisation de macros dans ce contexte par rapport à la suggestion de @ paercebal? Sinon, il semble que les macros sont en fait gratuites.
einpoklum
1
#ifdef WE_ARE_ON_WIN32plz :)
Courses de légèreté en orbite
38

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

#define ASSERT_THROW(condition) \
if (!(condition)) \
     throw std::exception(#condition " is false");
Motti
la source
5
Juste un petit bout, mais personnellement, je laisserais le point-virgule désactivé.
Michael Myers
10
Je suis d'accord, en fait je le mettrais dans un do {} while (false) (pour éviter le highjacking) mais je voulais faire simple.
Motti
33

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 chaînes littérales peuvent être facilement concaténées .

#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 a const char *était utilisé, une sorte de classe de chaîne devrait être utilisée pour effectuer la concaténation lors 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);
Motti
la source
2
Dans C ++ 11, je considère que c'est la partie la plus importante (autre que l'inclusion des gardes). Les macros sont vraiment la meilleure chose que nous ayons pour le traitement des chaînes à la compilation. C'est une fonctionnalité que j'espère que nous obtiendrons en C ++ 11 ++
David Stone
1
C'est la situation qui m'a amené à souhaiter des macros en C #.
Rawling
2
J'aimerais pouvoir +42 ceci. Un aspect très important, mais pas souvent rappelé des littéraux de chaîne.
Daniel Kamil Kozar
24

Lorsque vous souhaitez modifier le flux de programme ( return, breaket continue) le code dans une fonction se comporte différemment du code qui est en fait incorporé 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.
Motti
la source
Lancer une exception me semble être une meilleure alternative.
einpoklum
Lors de l'écriture d'extensions python C (++), les exceptions sont propagées en définissant une chaîne d'exception, puis en retournant -1ou NULL. Une macro peut donc réduire considérablement le code passe-partout.
black_puppydog
20

Les évidents incluent les gardes

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif
Kena
la source
17

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
1800 INFORMATIONS
la source
3
Peut-être un point plus général: les fonctions évaluent leurs arguments exactement une fois. Les macros peuvent évaluer les arguments plus ou moins de fois.
Steve Jessop
@ [Greg Rogers] tout ce que le préprocesseur de macro fait est de remplacer du texte. Une fois que vous avez compris cela, il ne devrait plus y avoir de mystère à ce sujet.
1800 INFORMATION
Vous pouvez obtenir le comportement équivalent en modélisant andf au lieu de forcer l'évaluation à bool avant d'appeler la fonction. Je n'aurais pas réalisé que ce que vous avez dit était vrai sans l'essayer par moi-même. Intéressant.
Greg Rogers
Comment pouvez-vous faire cela avec un modèle?
1800 INFORMATION
6
Cacher les opérations de court-circuit derrière une macro de style de fonction est l'une des choses que je ne veux vraiment pas voir dans le code de production.
MikeMB
17

Disons que nous ignorerons les 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é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)

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 mélanger des modèles avec des macros peut conduire à 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 avez besoin du __FILE__ et / ou du __LINE__ d'un code, 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 en utilisant les opérateurs de concaténation # et ##, comme générer des getters et des setters pour une propriété (c'est pour un cas assez limité, à travers).

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

(encore, je n'ai vu ce genre de code correctement utilisé qu'une seule fois )

Dernier point, mais non le moindre, le fameux 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 Boost)

Ce qui est (à mon humble avis) 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 fait des restes de code C jamais traduit en C ++ approprié.

paercebal
la source
1
Utilisez le CPP uniquement pour ce que le compilateur ne peut pas faire. Par exemple, RAISE_ERROR_STL doit utiliser le CPP uniquement pour déterminer la signature de fichier, de ligne et de fonction, et les transmettre à une fonction (éventuellement en ligne) qui s'occupe du reste.
Rainer Blome le
Veuillez mettre à jour votre réponse pour refléter C ++ 11 et adresser le commentaire de @ RainerBlome.
einpoklum
@RainerBlome: Nous sommes d'accord. La macro RAISE_ERROR_STL est antérieure à C ++ 11, donc dans ce contexte, elle est pleinement justifiée. Ma compréhension (mais je n'ai jamais eu l'occasion de traiter ces fonctionnalités spécifiques) est que vous pouvez utiliser des modèles variadiques (ou des macros?) En C ++ moderne pour résoudre le problème de manière plus élégante.
paercebal
@einpoklum: "Veuillez mettre à jour votre réponse pour refléter C ++ 11 et répondre au commentaire de RainerBlome" Non :-). . . Je pense que, au mieux, j'ajouterai une section pour le C ++ moderne, avec des implémentations alternatives réduisant ou éliminant le besoin de macros, mais le fait est que les macros sont laides et mauvaises, mais lorsque vous devez faire quelque chose, le compilateur ne comprend pas , vous le faites via des macros.
paercebal
Même avec C ++ 11, une grande partie de ce que fait votre macro peut être laissée à une fonction: de #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; }cette façon, la macro est beaucoup plus courte.
Rainer Blome
16

Les frameworks de test unitaire pour C ++ comme UnitTest ++ tournent à peu près autour de macros de préprocesseur. Quelques lignes de code de test unitaire se développent dans une hiérarchie de classes qu'il ne serait pas du tout amusant de taper manuellement. Sans quelque chose comme UnitTest ++ et c'est la magie du préprocesseur, je ne sais pas comment vous pourriez écrire efficacement des tests unitaires pour C ++.

Joe
la source
Unittests est parfaitement possible d'écrire sans framework. En fin de compte, cela ne dépend vraiment que du type de sortie que vous souhaitez. Si vous ne vous en souciez pas, une simple valeur de sortie indiquant le succès ou l'échec devrait parfaitement convenir.
Plus clair le
15

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 du programmeur} 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 embarqués, C est la seule option en dehors de l'assembleur de formulaires. Après avoir programmé sur le bureau avec C ++, puis basculé vers des cibles intégrées plus petites, vous apprenez à cesser de vous soucier des «inélégances» de tant de fonctionnalités C nues (macros incluses) et d'essayer simplement de déterminer la meilleure utilisation sûre que vous pouvez en tirer. fonctionnalités.

Alexander Stepanov dit :

Lorsque nous programmons en C ++, nous ne devons pas avoir honte de son héritage 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 cohérents avec leur propre logique.

VictorH
la source
Je pense que c'est la mauvaise attitude. Ce n'est pas parce que vous pouvez apprendre à «le gérer correctement» que cela vaut le temps et les efforts de quiconque.
Neil G
9

Nous utilisons les macros __FILE__et __LINE__à des fins de diagnostic dans le lancement, la capture et la journalisation d'exceptions riches en informations, ainsi que des analyseurs de fichiers journaux automatisés dans notre infrastructure d'assurance qualité.

Par exemple, une macro de lancement OUR_OWN_THROWpeut être utilisée avec un type d'exception et des paramètres de constructeur pour cette exception, y compris une description textuelle. Comme ça:

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

Cette macro lancera bien sûr l' InvalidOperationExceptionexception avec la description comme paramètre du constructeur, mais elle écrira également un message dans un fichier journal composé du nom du fichier et du numéro de ligne où le lancer s'est produit et de sa description textuelle. L'exception levée obtiendra un identifiant, qui sera également enregistré. Si l'exception est déjà interceptée ailleurs dans le code, elle sera marquée comme telle et le fichier journal indiquera alors que cette exception spécifique a été gérée et qu'il est donc peu probable que ce soit la cause d'un crash qui pourrait être connecté plus tard. Les exceptions non gérées peuvent être facilement détectées par notre infrastructure QA automatisée.

Johann Gerell
la source
9

Répétition du code.

Jetez un oeil pour booster 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.

Ruggero Turra
la source
J'ai presque tous, sinon tous, les cas - la répétition de code peut être évitée avec des appels de fonction.
einpoklum
@einpoklum: Je ne suis pas d'accord. Jetez un œil au lien
Ruggero Turra
9

Certains éléments très avancés et utiles peuvent encore être construits en utilisant un 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 d'un identifiant C et d'une chaîne

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

Booster la métaprogrammation du préprocesseur

Suma
la source
Le troisième lien est rompu pour info
Robin Hartland
Jetez un œil stdio.het sal.henregistrez-vous vc12pour mieux comprendre.
Elshan
7

J'utilise occasionnellement des macros pour pouvoir définir des informations en un seul endroit, mais je les utilise de différentes manières dans différentes parties du code. Ce n'est que légèrement 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 d'initialisation 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"
Andrew Johnson
la source
1
Remarque: une technique similaire peut être mise en œuvre même sans inclusion distincte. Voir: stackoverflow.com/questions/147267/… stackoverflow.com/questions/126277/…
Suma
6

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 ignorer explicitement la sécurité de type. 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.

Doug T.
la source
Comme le questionneur l'a demandé: cela peut être fait sans macros en incluant différents en-têtes via différents chemins d'inclusion par plate-forme. J'ai tendance à convenir que les macros sont souvent plus pratiques.
Steve Jessop
Je suis d'accord. Si vous commencez à utiliser des macros à cette fin, le code peut rapidement devenir beaucoup moins lisible
Nemanja Trifunovic
6

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

Keshi
la source
Pourquoi réinventeriez-vous la roue alors que les implémentations de la bibliothèque standard fournissent déjà via <cassert>la assert()macro, qui vide les informations de fichier / ligne / fonction? (dans toutes les implémentations que j'ai vues, de toute façon)
underscore_d
4
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

Contrairement à la solution de modèle `` préférée '' présentée dans un thread actuel, vous pouvez l'utiliser comme expression constante:

char src[23];
int dest[ARRAY_SIZE(src)];
pétillants
la source
2
Cela peut être fait avec des modèles d'une manière plus sûre (qui ne se compilera pas si un pointeur est passé plutôt qu'un tableau) stackoverflow.com/questions/720077/calculating-size-of-an-array/...
Motti
1
Maintenant que nous avons constexpr en C ++ 11, la version sûre (non macro) peut également être utilisée dans une expression constante. template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
David Stone
3

Vous pouvez utiliser #defines pour vous aider avec le débogage et les scénarios 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

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.

Andrew Johnson
la source
3

Lorsque vous prenez une décision au moment de la compilation sur le comportement spécifique au compilateur / OS / 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
Loki Astari
la source
3

J'utilise des macros pour définir facilement les 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;\
    };\
  }\
MrBeast
la source
2

Les compilateurs peuvent refuser votre demande de mise en ligne.

Les macros auront toujours leur place.

Quelque chose que je trouve utile est #define DEBUG pour le traçage de débogage - vous pouvez le laisser 1 pendant le débogage d'un problème (ou même le laisser allumé pendant tout le cycle de développement), puis le désactiver quand il est temps d'expédier.

lourd
la source
10
Si le compilateur refuse votre demande de mise en ligne, cela peut avoir une très bonne raison. Un bon compilateur sera meilleur pour l'inlining correctement que vous, et un mauvais vous posera plus de problèmes de performances que cela.
David Thornley
@DavidThornley Ou ce n'est peut-être pas un excellent compilateur d'optimisation comme GCC ou CLANG / LLVM. Certains compilateurs ne sont que de la merde.
Miles Rout
2

Dans mon dernier emploi, je travaillais sur un antivirus. Pour me faciliter le débogage, j'avais beaucoup de journalisation bloquée partout, mais dans une application à forte demande comme celle-ci, les frais d'un appel de fonction sont tout simplement trop chers. Donc, j'ai créé cette petite macro, qui m'a quand même permis d'activer la journalisation du débogage sur une version commerciale sur un site client, sans le coût d'un appel de fonction, vérifierait l'indicateur de débogage et retournerait 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 VA_ARGS dans les fonctions de journal, c'était un bon cas pour une macro comme celle-ci.

Avant cela, j'utilisais une macro dans une application de haute sécurité qui devait dire à l'utilisateur qu'il n'avait pas le bon accès, et cela lui disait de quel drapeau il avait besoin.

La ou les macro (s) définies 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 vérifications sur toute l'interface utilisateur, et cela vous indiquerait quels rôles étaient autorisés à effectuer l'action que vous avez essayé de faire, si vous ne possédiez pas déjà ce rôle. La raison pour deux d'entre eux était de renvoyer une valeur à certains endroits, et de revenir d'une fonction vide à 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 sais pas comment cela aurait pu être aidé avec des modèles ... A part ça, j'essaye de les éviter, à moins que VRAIMENT nécessaire.

LarryF
la source
2

Encore une 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

Pas dans la liste
la source
2

Peut-être que la meilleure utilisation 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: --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

Beaucoup plus lisible que de l'implémenter autrement, à mon avis.

rkellerm
la source
2

Il semble que VA_ARGS n'a é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 pourrait également être incorporé dans la get_op_from_namefonction hypothétique . 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 variadiques avec C ++ 11, nous pouvons résoudre cela "correctement" avec un modèle.

Martin Ba
la source
1

Je pense que cette astuce est une utilisation intelligente du préprocesseur qui ne peut pas être émulée 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.

Mathieu Pagé
la source
2
Cette astuce ne fonctionne pas selon la norme. Il tente de créer un marqueur de commentaire via le préprocesseur, mais les commentaires doivent être supprimés avant l'exécution du préprocesseur. Un compilateur conforme provoquera ici une erreur de syntaxe.
David Thornley
2
Désolé David mais le compilateur doit contenir une deuxième copie de la suppression des commentaires.
Joshua
beaucoup plus facile est de faire de l'indicateur de débogage un bool global const et d'utiliser un code comme celui-ci: if (debug) cout << "..."; - pas besoin de macros!
Stefan Monov
@Stefan: En effet, c'est ce que je fais maintenant. Tout compilateur décent ne générera aucun code si le débogage est faux dans ce cas.
Mathieu Pagé
1

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

bk1e
la source