web-dev-qa-db-fra.com

constante statique vs #define

Est-il préférable d'utiliser static const vars que #define préprocesseur? Ou peut-être que cela dépend du contexte?

Quels sont les avantages/inconvénients pour chaque méthode?

184
Patrice Bernassola

Personnellement, je déteste le préprocesseur, donc je vais toujours avec const.

Le principal avantage de #define est qu’elle ne nécessite aucune mémoire pour être stockée dans votre programme, car elle remplace en réalité un texte par une valeur littérale. Il a également l'avantage de ne pas avoir de type et qu'il peut donc être utilisé pour toute valeur entière sans générer d'avertissements.

Les avantages des "const" sont qu'ils peuvent être étendus et peuvent être utilisés dans des situations où un pointeur sur un objet doit être passé.

Je ne sais pas exactement où vous voulez en venir avec la partie "statique". Si vous déclarez globalement, je le mettrais dans un espace de nom anonyme au lieu d'utiliser statique. Par exemple

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}
126
T.E.D.

Avantages et inconvénients de tout, en fonction de l'utilisation:

  • enums
    • possible uniquement pour les valeurs entières
    • problèmes de collision correctement ciblés/identificateurs traités correctement, en particulier dans les classes d'énumération C++ 11 où les énumérations de enum class X sont non ambiguës par la portée X::
    • fortement typé, mais à une taille assez grande, signée ou non signée, sur laquelle vous n’avez aucun contrôle en C++ 03 (bien que vous puissiez spécifier un champ de bits dans lequel elles doivent être emballées si l’énumération est membre de struct/classe/union), alors que C++ 11 est défini par défaut sur int mais peut être défini explicitement par le programmeur
    • ne peut pas prendre l'adresse - il n'y en a pas car les valeurs d'énumération sont effectivement substituées en ligne aux points d'utilisation
    • restrictions d'utilisation plus strictes (par exemple, incrémentation - template <typename T> void f(T t) { cout << ++t; } ne compilera pas, bien que vous puissiez envelopper une énumération dans une classe avec un constructeur implicite, un opérateur de transtypage et des opérateurs définis par l'utilisateur)
    • le type de chaque constante est tiré de l'énumération englobante. Ainsi, template <typename T> void f(T) obtient une instanciation distincte lorsque la même valeur numérique est passée à partir de différentes énumérations, qui sont toutes distinctes de toute instanciation f(int) réelle. Le code objet de chaque fonction peut être identique (en ignorant les décalages d'adresse), mais je ne m'attendrais pas à ce qu'un compilateur/éditeur de liens élimine les copies inutiles, bien que vous puissiez vérifier votre compilateur/éditeur de liens si vous le souhaitez.
    • même avec typeof/decltype, on ne peut pas s’attendre à ce que numeric_limits fournisse des informations utiles sur l’ensemble des valeurs et combinaisons significatives (en effet, les combinaisons "légales" ne sont même pas notées dans le code source, considérez enum { A = 1, B = 2 } - est A|B "légal" à partir d’un perspective du programme?)
    • le nom de type de l'énumération peut apparaître à différents endroits dans RTTI, dans les messages du compilateur, etc. - éventuellement utile, éventuellement obscurcissant
    • vous ne pouvez pas utiliser une énumération sans que l'unité de traduction voie réellement la valeur, ce qui signifie que les énumérations dans les API de bibliothèque ont besoin des valeurs exposées dans l'en-tête, et que make et d'autres outils de recompilation basés sur un horodatage déclenchent la recompilation du client lorsqu'ils sont modifiés (mauvais !)
  • consts
    • problèmes de conflit correctement ciblés/identifiants bien gérés
    • type fort, unique, spécifié par l'utilisateur
      • vous pouvez essayer de "taper" un #define ala #define S std::string("abc"), mais la constante évite la construction répétée de temporaires distincts à chaque point d'utilisation
    • Complications d'une règle de définition
    • peut prendre l'adresse, créer des références const à eux etc.
    • ressemblant le plus à une valeur non -const, ce qui minimise le travail et l’impact si la commutation entre les deux
    • la valeur peut être placée dans le fichier d'implémentation, permettant une recompilation localisée et uniquement des liens client pour prendre en compte les modifications
  • définit
    • portée "globale"/plus sujette à des utilisations conflictuelles, pouvant générer des problèmes de compilation difficiles à résoudre et des résultats inattendus au moment de l’exécution plutôt que des messages d’erreur sains; atténuer cela nécessite:
      • les identifiants longs, obscurs et/ou coordonnés de manière centrale, et leur accès ne peuvent pas bénéficier de la mise en correspondance implicite des espaces de noms utilisé/actuel/Koenig-recherché, des alias d'espaces de noms, etc.
      • bien que la meilleure pratique de contournement permette aux identifiants de paramètre de modèle d'être des lettres majuscules à un caractère (éventuellement suivies d'un nombre), une autre utilisation d'identificateurs sans lettres minuscules est généralement réservée aux définitions du préprocesseur (en dehors des bibliothèques OS et C/C++ en-têtes). Cela est important pour que l'utilisation des pré-processeurs à l'échelle de l'entreprise reste gérable. On peut s’attendre à ce que les bibliothèques tierces se conforment. Observer cela implique que la migration des consts ou des énumérations existants vers/depuis définit implique un changement de capitalisation et nécessite par conséquent des modifications du code source du client plutôt qu'une simple recompilation. (Personnellement, je mets en majuscule la première lettre des énumérations, mais pas les const., Donc je migrerais entre ces deux aussi - peut-être le temps de repenser cela.)
    • plusieurs opérations de compilation possibles: concaténation de littéraux de chaîne, stringification (en prenant leur taille), concaténation en identificateurs L’inconvénient est que, étant donné #define X "x" et une certaine utilisation du client ala "pre" X "post", si vous voulez ou devez faire de X une variable modifiable à l’exécution plutôt que constante, vous forcez les modifications au code client (plutôt que la simple recompilation), alors que cette transition est plus facile à partir d'un const char* ou const std::string étant donné qu'ils obligent déjà l'utilisateur à incorporer des opérations de concaténation (par exemple, "pre" + X + "post" pour string)
      • non typé (GCC ne prévient pas si comparé à sizeof)
    • certaines chaînes de compilateur/éditeur de liens/débogueur peuvent ne pas présenter l'identifiant, vous serez donc réduit à regarder des "nombres magiques" (chaînes, peu importe ...)
    • ne peut pas prendre l'adresse
    • la valeur substituée n'a pas besoin d'être légale (ni discrète) dans le contexte où la #define est créée, car elle est évaluée à chaque point d'utilisation. Vous pouvez donc référencer des objets non encore déclarés, en fonction de "l'implémentation" qui ne nécessite pas être pré-inclus, créer des "constantes" telles que { 1, 2 } pouvant être utilisées pour initialiser des tableaux, ou #define MICROSECONDS *1E-6 etc. ( définitivement ne le recommandant pas!)
    • certaines choses spéciales comme __FILE__ et __LINE__ peuvent être incorporées dans la substitution de macros
    • vous pouvez tester l'existence et la valeur dans les instructions #if pour l'inclusion conditionnelle de code (plus puissant qu'un "si" de post-traitement préalable, car le code n'a pas besoin d'être compilable s'il n'est pas sélectionné par le pré-processeur), utilisez #undef-ine, redéfinissez, etc.
    • le texte substitué doit être exposé:
    • dans l'unité de traduction utilisée, ce qui signifie que les macros dans les bibliothèques destinées au client doivent figurer dans l'en-tête; par conséquent, unsigned et d'autres outils de recompilation basés sur l'horodatage déclencheront une recompilation du client quand ils seront modifiés (mauvais!)
      • en règle générale, j'utilise makes et je les considère comme l'option la plus professionnelle pour un usage général (même si les autres ont une simplicité qui séduit ce vieux programmeur paresseux).
      • or on the command line, where even more care is needed to make sure client code is recompiled (e.g. the Makefile or script supplying the definition should be listed as a dependency)

As a general rule, I use consts and consider them the most professional option for general usage (though the others have a simplicity appealing to this old lazy programmer).

213
Tony Delroy

S'il s'agit d'une question C++ et qu'elle mentionne #define en guise d'alternative, il s'agit alors de constantes "globales" (c'est-à-dire de la portée du fichier) et non de membres de la classe. En ce qui concerne de telles constantes en C++, static const est redondant. En C++, const a un lien interne par défaut et il est inutile de les déclarer static. Donc, il s’agit vraiment de const contre #define

Et, finalement, en C++, const est préférable. Du moins parce que ces constantes sont typées et étendues. Il n'y a tout simplement aucune raison de préférer #define à const, à quelques exceptions près.

Les constantes de chaîne, BTW, sont un exemple d'une telle exception. Avec les constantes de chaîne #defined, on peut utiliser la fonctionnalité de concaténation au moment de la compilation des compilateurs C/C++, comme dans

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

P.S. Encore une fois, juste au cas où, quand quelqu'un mentionne static const comme alternative à #define, cela signifie généralement qu'ils parlent de C, pas de C++. Je me demande si cette question est étiquetée correctement ...

41
AnT

Utiliser un const statique revient à utiliser n'importe quelle autre variable const dans votre code. Cela signifie que vous pouvez suivre d'où provient l'information, par opposition à une #define qui sera simplement remplacée dans le code lors du processus de pré-compilation.

Vous voudrez peut-être jeter un coup d'œil à la version C++ FAQ pour cette question: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7

5
Percutio
  • Une constante statique est typée (elle a un type) et peut être vérifiée par le compilateur pour la validité, la redéfinition, etc. 
  • un #define peut être redéfini indéfiniment que ce soit.

Habituellement, vous devriez préférer les constants statiques. Cela n'a pas d'inconvénient. Le pré-processeur devrait être principalement utilisé pour la compilation conditionnelle (et parfois pour des trics vraiment sales).

4
RED SOFT ADAIR

La définition de constantes à l'aide de la directive de préprocesseur #define n'est pas recommandée pour s'appliquer non seulement dans C++, mais également dans C. Ces constantes n'auront pas le type. Même dans C, il a été proposé d'utiliser const pour les constantes.

1
Aleksey Bykov

#define peut conduire à des résultats inattendus:

#include <iostream>

#define x 500
#define y x + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Affiche un résultat incorrect:

y is 505
z is 510

Cependant, si vous remplacez cela par des constantes:

#include <iostream>

const int x = 500;
const int y = x + 5;

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Il produit le résultat correct:

y is 505
z is 1010

En effet, #define remplace simplement le texte. Parce que cela peut sérieusement gâcher l'ordre des opérations, je recommanderais plutôt d'utiliser une variable constante.

1
Juniorized

S'il vous plaît voir ici: static const vs define

généralement une déclaration const (notez qu'il n'est pas nécessaire qu'elle soit statique) est la voie à suivre

1
ennuikiller

Préférez toujours utiliser les fonctionnalités du langage par rapport à certains outils supplémentaires tels que le préprocesseur.

ES.31: n'utilisez pas de macros pour des constantes ou des "fonctions"

Les macros sont une source majeure de bugs. Les macros n'obéissent pas à la portée habituelle et règles de type. Les macros n'obéissent pas aux règles habituelles d'argumentation qui passe. Les macros garantissent que le lecteur humain voit quelque chose de différent de ce que voit le compilateur. Les macros compliquent la construction des outils.

From C++ Core Guidelines

0
Hitokage

Si vous définissez une constante à partager entre toutes les instances de la classe, utilisez static const. Si la constante est spécifique à chaque instance, utilisez simplement const (mais notez que tous les constructeurs de la classe doivent initialiser cette variable membre const dans la liste d'initialisation).

0
snr