web-dev-qa-db-fra.com

Constexpr vs macros

Où devrais-je préférer utiliser macros et où devrais-je préférer constexpr? Ne sont-ils pas fondamentalement les mêmes?

#define MAX_HEIGHT 720

contre

constexpr unsigned int max_height = 720;
54
Tom Dorone

Ne sont-ils pas fondamentalement les mêmes?

Non, absolument pas. Pas même proche.

En dehors du fait que votre macro est un int et votre constexpr unsigned est un unsigned, il y a des différences importantes et les macros ont seulement n avantage.

Portée

Une macro est définie par le préprocesseur et est simplement substituée dans le code chaque fois que cela se produit. Le préprocesseur est dumb et ne comprend pas la syntaxe ou la sémantique C++. Les macros ignorent les portées telles que les espaces de noms, les classes ou les blocs de fonction. Vous ne pouvez donc utiliser un nom pour rien d'autre dans un fichier source. Ce n'est pas vrai pour une constante définie comme une variable C++ appropriée:

#define MAX_HEIGHT 720
constexpr int max_height = 720;

class Window {
  // ...
  int max_height;
};

C'est bien d'avoir une variable de membre appelée max_height parce qu'il s'agit d'un membre de la classe et qu'il a donc une portée différente et est distinct de celui de la portée de l'espace de noms. Si vous avez essayé de réutiliser le nom MAX_HEIGHT pour le membre, le préprocesseur le changerait en un non-sens qui ne compilerait pas:

class Window {
  // ...
  int 720;
};

C'est pourquoi vous devez donner des macros UGLY_SHOUTY_NAMES pour vous assurer qu'ils se démarquent et vous pouvez être prudent en les nommant pour éviter les conflits. Si vous n'utilisez pas de macros inutilement, vous n'avez pas à vous en préoccuper (et vous n'avez pas à lire SHOUTY_NAMES).

Si vous voulez simplement une constante dans une fonction, vous ne pouvez pas le faire avec une macro, car le préprocesseur ne sait pas ce qu'est une fonction ni ce que signifie y être. Pour limiter une macro à une partie du fichier, vous devez #undef encore

int limit(int height) {
#define MAX_HEIGHT 720
  return std::max(height, MAX_HEIGHT);
#undef MAX_HEIGHT
}

Comparez au plus raisonnable:

int limit(int height) {
  constexpr int max_height = 720;
  return std::max(height, max_height);
}

Pourquoi préféreriez-vous la macro?

Un véritable emplacement mémoire

Une variable constexpr est une variable . Elle existe donc dans le programme et vous pouvez faire des choses normales en C++, comme prendre son adresse et y lier une référence.

Ce code a un comportement indéfini:

#define MAX_HEIGHT 720
int limit(int height) {
  const int& h = std::max(height, MAX_HEIGHT);
  // ...
  return h;
}

Le problème est que MAX_HEIGHT n'est pas une variable, donc pour l'appel à std::max un temporaire int doit être créé par le compilateur. La référence renvoyée par std::max pourrait alors faire référence à ce temporaire, qui n’existe plus à la fin de cette instruction, donc return h accède à la mémoire invalide.

Ce problème n’existe tout simplement pas avec une variable appropriée, car elle a un emplacement fixe en mémoire qui ne disparaît pas:

int limit(int height) {
  constexpr int max_height = 720;
  const int& h = std::max(height, max_height);
  // ...
  return h;
}

(En pratique, vous déclareriez probablement int h ne pas const int& h mais le problème peut survenir dans des contextes plus subtils.)

Conditions du préprocesseur

Le seul moment pour préférer une macro est lorsque vous avez besoin que sa valeur soit comprise par le préprocesseur, pour pouvoir être utilisée dans #if conditions, par exemple.

#define MAX_HEIGHT 720
#if MAX_HEIGHT < 256
using height_type = unsigned char;
#else
using height_type = unsigned int;
#endif

Vous ne pouvez pas utiliser de variable ici, car le préprocesseur ne comprend pas comment faire référence à des variables par leur nom. Il ne comprend que des choses de base, telles que le développement de macros et les directives commençant par # (comme #include et #define et #if).

Si vous voulez une constante qui puisse être comprise par le préprocesseur , vous devez utiliser le préprocesseur pour la définir. Si vous voulez une constante pour le code C++ normal, utilisez du code C++ normal.

L'exemple ci-dessus est juste pour démontrer une condition de préprocesseur, mais même ce code pourrait éviter d'utiliser le préprocesseur:

using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
102
Jonathan Wakely

De manière générale, vous devriez utiliser constexpr chaque fois que vous le pouvez, et les macros uniquement si aucune autre solution n'est possible.

Raisonnement:

Les macros sont un simple remplacement dans le code et, pour cette raison, elles génèrent souvent des conflits (par exemple, windows.h max macro vs std::max). De plus, une macro qui fonctionne peut facilement être utilisée de manière différente, ce qui peut ensuite provoquer d’étranges erreurs de compilation. (par exemple. Q_PROPERTY utilisé sur les membres de la structure)

En raison de toutes ces incertitudes, c’est un bon style de code pour éviter les macros, exactement comme vous le feriez habituellement.

constexpr est défini sémantiquement et génère donc beaucoup moins de problèmes.

6
Adrian Maire

Grande réponse par Jonathon Wakely . Je vous conseillerais également de jeter un œil à réponse de jogojapan quant à la différence entre const et constexpr avant même d'envisager l'utilisation de macros .

Les macros sont stupides, mais d'une manière bonne . Apparemment, de nos jours, ils constituent une aide à la construction lorsque vous souhaitez que des parties très spécifiques de votre code ne soient compilées que si certains paramètres de construction sont "définis". Habituellement, tout ce que cela signifie, c'est de prendre votre nom de macro ou, mieux encore, appelons-le un Trigger et d'ajouter des éléments comme, /D:Trigger, -DTrigger, etc. aux outils de construction utilisés.

Bien qu'il existe de nombreuses utilisations différentes pour les macros, ce sont les deux pratiques que je vois le plus souvent qui ne sont pas mauvaises ou obsolètes:

  1. Sections de code spécifiques au matériel et à la plate-forme
  2. Augmentation de la verbosité

Ainsi, bien que vous puissiez, dans le cas du PO, atteindre le même objectif de définition d'un int avec constexpr ou d'un MACRO, il est peu probable que les deux se chevauchent lors de l'utilisation de conventions modernes. Voici une macro-utilisation courante qui n’a pas encore été supprimée.

#if defined VERBOSE || defined DEBUG || defined MSG_ALL
    // Verbose message-handling code here
#endif

Autre exemple d'utilisation de macros, supposons que vous disposiez d'un nouveau matériel à publier, ou peut-être d'une génération spécifique de celui-ci comportant des solutions de contournement complexes dont les autres n'ont pas besoin. Nous définirons cette macro comme GEN_3_HW.

#if defined GEN_3_HW && defined _WIN64
    // Windows-only special handling for 64-bit upcoming hardware
#Elif defined GEN_3_HW && defined __Apple__
    // Special handling for macs on the new hardware
#Elif !defined _WIN32 && !defined __linux__ && !defined __Apple__ && !defined __Android__ && !defined __unix__ && !defined __arm__
    // Greetings, Outlander! ;)
#else
    // Generic handling
#endif
2
kayleeFrye_onDeck