J'ai toujours posé cette question mais je n'ai jamais reçu de très bonne réponse; Je pense que presque tous les programmeurs avant même d'écrire le premier "Hello World" avaient rencontré une phrase comme "macro ne devrait jamais être utilisée", "macro are evil" et ainsi de suite, ma question est: pourquoi? Avec le nouveau C ++ 11, y a-t-il une vraie alternative après tant d'années?
La partie la plus simple concerne les macros telles #pragma
que, qui sont spécifiques à la plate-forme et au compilateur, et la plupart du temps, elles ont de graves défauts comme #pragma once
celui-ci est sujet aux erreurs dans au moins 2 situations importantes: le même nom dans des chemins différents et avec certaines configurations réseau et systèmes de fichiers.
Mais en général, qu'en est-il des macros et des alternatives à leur utilisation?
la source
#pragma
n'est pas macro.#pragma
.constexpr
, lesinline
fonctions ettemplates
, maisboost.preprocessor
etchaos
montrer que les macros ont leur place. Sans parler des macros de configuration pour les compilateurs de différence, les plates-formes, etc.Réponses:
Les macros sont comme n'importe quel autre outil - un marteau utilisé dans un meurtre n'est pas mauvais parce que c'est un marteau. C'est mauvais dans la manière dont la personne l'utilise de cette manière. Si vous voulez enfoncer des clous, un marteau est un outil parfait.
Il y a quelques aspects des macros qui les rendent "mauvaises" (je les développerai plus tard et suggérerai des alternatives):
Alors développons un peu ici:
1) Les macros ne peuvent pas être déboguées. Lorsque vous avez une macro qui se traduit par un nombre ou une chaîne, le code source aura le nom de la macro et de nombreux débogueurs, vous ne pouvez pas "voir" ce que la macro se traduit. Donc, vous ne savez pas vraiment ce qui se passe.
Remplacement : utilisez
enum
ouconst T
Pour les macros «fonctionnelles», comme le débogueur fonctionne au niveau «par ligne source où vous êtes», votre macro agira comme une seule instruction, qu'il s'agisse d'une instruction ou d'une centaine. Rend difficile de comprendre ce qui se passe.
Remplacement : utilisez des fonctions - en ligne si cela doit être "rapide" (mais attention, trop de fonctions en ligne n'est pas une bonne chose)
2) Les extensions de macros peuvent avoir des effets secondaires étranges.
Le célèbre est
#define SQUARE(x) ((x) * (x))
et l'utilisationx2 = SQUARE(x++)
. Cela conduit àx2 = (x++) * (x++);
ce qui, même s'il s'agissait de code valide [1], ne serait certainement pas ce que le programmeur voulait. Si c'était une fonction, ce serait bien de faire x ++, et x n'incrémenterait qu'une seule fois.Un autre exemple est "if else" dans les macros, disons que nous avons ceci:
puis
Cela devient en fait complètement faux ...
Remplacement : de vraies fonctions.
3) Les macros n'ont pas d'espace de noms
Si nous avons une macro:
et nous avons du code en C ++ qui utilise begin:
Maintenant, quel message d'erreur pensez-vous obtenir, et où recherchez-vous une erreur [en supposant que vous avez complètement oublié - ou même pas au courant - la macro de début qui se trouve dans un fichier d'en-tête écrit par quelqu'un d'autre? [et encore plus amusant si vous incluiez cette macro avant l'inclusion - vous seriez noyé dans des erreurs étranges qui n'ont absolument aucun sens quand vous regardez le code lui-même.
Remplacement : Eh bien, il n'y a pas tant un remplacement qu'une «règle» - n'utilisez que des noms en majuscules pour les macros, et n'utilisez jamais tous les noms en majuscules pour d'autres choses.
4) Les macros ont des effets que vous ne réalisez pas
Prenez cette fonction:
Maintenant, sans regarder la macro, vous penseriez que begin est une fonction, qui ne devrait pas affecter x.
Ce genre de chose, et j'ai vu des exemples beaucoup plus complexes, peut VRAIMENT gâcher votre journée!
Remplacement : n'utilisez pas de macro pour définir x ou transmettez x comme argument.
Il y a des moments où l'utilisation de macros est vraiment bénéfique. Un exemple consiste à envelopper une fonction avec des macros pour transmettre des informations de fichier / ligne:
Maintenant, nous pouvons utiliser
my_debug_malloc
comme malloc régulier dans le code, mais il a des arguments supplémentaires, donc quand il s'agit de la fin et que nous analysons les "éléments de mémoire qui n'ont pas été libérés", nous pouvons afficher où l'allocation a été faite afin que le le programmeur peut localiser la fuite.[1] Il est indéfini de mettre à jour une variable plus d'une fois "dans un point de séquence". Un point de séquence n'est pas exactement la même chose qu'une instruction, mais dans la plupart des cas, c'est ce que nous devrions considérer comme. Cela
x++ * x++
entraînera une mise à jourx
deux fois, ce qui n'est pas défini et entraînera probablement des valeurs différentes sur différents systèmes,x
ainsi qu'une valeur de résultat différente .la source
if else
problèmes peuvent être résolus en enveloppant le corps de la macro à l'intérieurdo { ... } while(0)
. Cela se comporte comme on pourrait s'y attendre en ce qui concerneif
etfor
et d'autres problèmes de flux de contrôle potentiellement risqués. Mais oui, une vraie fonction est généralement une meilleure solution.#define macro(arg1) do { int x = func(arg1); func2(x0); } while(0)
note: expanded from macro 'begin'
et montreront oùbegin
est défini.Le dicton «les macros sont mauvaises» se réfère généralement à l'utilisation de #define, pas de #pragma.
Plus précisément, l'expression fait référence à ces deux cas:
définition des nombres magiques sous forme de macros
utilisation de macros pour remplacer des expressions
Oui, pour les éléments de la liste ci-dessus (les nombres magiques doivent être définis avec const / constexpr et les expressions doivent être définies avec les fonctions [normal / inline / template / inline template].
Voici quelques-uns des problèmes introduits en définissant des nombres magiques comme des macros et en remplaçant des expressions par des macros (au lieu de définir des fonctions pour évaluer ces expressions):
lors de la définition de macros pour les nombres magiques, le compilateur ne conserve aucune information de type pour les valeurs définies. Cela peut provoquer des avertissements de compilation (et des erreurs) et dérouter les personnes qui déboguent le code.
lors de la définition de macros au lieu de fonctions, les programmeurs qui utilisent ce code s'attendent à ce qu'elles fonctionnent comme des fonctions et ce n'est pas le cas.
Considérez ce code:
Vous vous attendez à ce que a et c soient 6 après l'affectation à c (comme il le ferait, en utilisant std :: max au lieu de la macro). Au lieu de cela, le code effectue:
En plus de cela, les macros ne prennent pas en charge les espaces de noms, ce qui signifie que la définition de macros dans votre code limitera le code client dans les noms qu'ils peuvent utiliser.
Cela signifie que si vous définissez la macro ci-dessus (pour max), vous ne pourrez plus le faire
#include <algorithm>
dans aucun des codes ci-dessous, sauf si vous écrivez explicitement:Avoir des macros au lieu de variables / fonctions signifie également que vous ne pouvez pas prendre leur adresse:
si une macro-constante est évaluée à un nombre magique, vous ne pouvez pas la passer par adresse
pour une macro-fonction, vous ne pouvez pas l'utiliser comme prédicat ou prendre l'adresse de la fonction ou la traiter comme un foncteur.
Edit: À titre d'exemple, la bonne alternative à ce qui
#define max
précède:Cela fait tout ce que fait la macro, avec une limitation: si les types d'arguments sont différents, la version du modèle vous oblige à être explicite (ce qui conduit en fait à un code plus sûr et plus explicite):
Si ce max est défini comme une macro, le code se compilera (avec un avertissement).
Si ce max est défini comme une fonction de modèle, le compilateur signalera l'ambiguïté, et vous devrez dire soit
max<int>(a, b)
oumax<double>(a, b)
(et donc explicitement indiquer votre intention).la source
const int someconstant = 437;
, et il peut être utilisé presque de toutes les manières dont une macro serait utilisée. De même pour les petites fonctions. Il y a quelques choses où vous pouvez écrire quelque chose comme une macro qui ne fonctionnera pas dans une expression régulière en C (vous pouvez faire quelque chose qui fait la moyenne d'un tableau de n'importe quel type de nombre, ce que C ne peut pas faire - mais C ++ a des modèles pour ça). Alors que C ++ 11 ajoute quelques autres choses que "vous n'avez pas besoin de macros pour cela", il est pour la plupart déjà résolu dans le C / C ++ antérieur.max
etmin
s'ils sont suivis d'une parenthèse gauche. Mais vous ne devriez pas définir de telles macros ...Un problème courant est le suivant:
Il imprimera 10, pas 5, car le préprocesseur l'étendra de cette façon:
Cette version est plus sûre:
la source
DIV
macro peut être réécrite avec une paire de () autourb
.#define DIV(a,b)
non#define DIV (a,b)
, ce qui est très différent.#define DIV(a,b) (a) / (b)
n'est pas assez bon; en#define DIV(a,b) ( (a) / (b) )
Les macros sont particulièrement utiles pour créer du code générique (les paramètres de macro peuvent être n'importe quoi), parfois avec des paramètres.
De plus, ce code est placé (c'est-à-dire inséré) au point où la macro est utilisée.
OTOH, des résultats similaires peuvent être obtenus avec:
fonctions surchargées (différents types de paramètres)
templates, en C ++ (types et valeurs de paramètres génériques)
fonctions en ligne (placez le code là où elles sont appelées, au lieu de passer à une définition en un seul point - cependant, c'est plutôt une recommandation pour le compilateur).
edit: quant à la raison pour laquelle les macro sont mauvaises:
1) pas de vérification de type des arguments (ils n'ont pas de type), donc peuvent être facilement mal utilisés 2) parfois se développer dans un code très complexe, qui peut être difficile à identifier et à comprendre dans le fichier prétraité 3) il est facile de faire une erreur -prone code dans les macros, comme:
puis appelez
qui se développe en
2 + 3 * 4 + 5 (et pas dans: (2 + 3) * (4 + 5)).
Pour avoir ce dernier, vous devez définir:
la source
Je ne pense pas qu'il y ait quelque chose de mal à utiliser des définitions ou des macros de préprocesseur comme vous les appelez.
C'est un concept de (méta) langage trouvé dans c / c ++ et comme tout autre outil, ils peuvent vous faciliter la vie si vous savez ce que vous faites. Le problème avec les macros est qu'elles sont traitées avant votre code c / c ++ et génèrent un nouveau code qui peut être défectueux et provoquer des erreurs de compilation qui sont tout sauf évidentes. Du bon côté, ils peuvent vous aider à garder votre code propre et vous faire économiser beaucoup de frappe s'il est utilisé correctement, donc cela dépend de vos préférences personnelles.
la source
Les macros en C / C ++ peuvent servir d'outil important pour le contrôle de version. Le même code peut être livré à deux clients avec une configuration mineure de macros. J'utilise des choses comme
Ce type de fonctionnalité n'est pas si facilement possible sans macros. Les macros sont en fait un excellent outil de gestion de la configuration logicielle et pas seulement un moyen de créer des raccourcis pour la réutilisation du code. La définition de fonctions à des fins de réutilisation dans les macros peut certainement créer des problèmes.
la source
Je pense que le problème est que les macros ne sont pas bien optimisées par le compilateur et sont "moche" à lire et à déboguer.
Les fonctions génériques et / ou les fonctions en ligne constituent souvent de bonnes alternatives.
la source
Les macros de préprocesseur ne sont pas mauvaises lorsqu'elles sont utilisées à des fins prévues telles que:
Alternatives - On peut utiliser une sorte de fichiers de configuration au format ini, xml, json à des fins similaires. Mais leur utilisation aura des effets d'exécution sur le code qu'une macro de préprocesseur peut éviter.
la source