Constexpr vs macros

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

#define MAX_HEIGHT 720

et

constexpr unsigned int max_height = 720;
33
demandé sur Tom Dorone 2017-02-22 12:50:19

3 réponses

ne sont-ils pas essentiellement les mêmes?

Non. Absolument pas. Même pas proche.

Outre le fait que votre macro est un int et constexpr unsigned est un unsigned, il y a des différences importantes et les macros n'ont que avantage.

Portée

une macro est définie par le préprocesseur et est simplement substituée dans le code à chaque fois qu'elle se produit. Le préprocesseur est muet et ne comprend pas la syntaxe C++ ou la sémantique. Les Macros ignorent les portées telles que les espaces de noms, les classes ou les blocs de fonctions, de sorte que vous ne pouvez pas utiliser un nom pour quoi que ce soit d'autre dans un fichier source. Ce n'est pas vrai pour une constante définie comme C++ variable:

#define MAX_HEIGHT 720
constexpr int max_height = 720;

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

c'est bien d'avoir une variable membre appelée max_height parce que c'est un membre de classe et a donc une portée différente, et est distinct de celui de namespace scope. Si vous avez essayé de réutiliser le nom MAX_HEIGHT pour le membre de la le préprocesseur le changerait en ce non-sens qui ne compilerait pas:

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

C'est pourquoi vous devez donner des macros UGLY_SHOUTY_NAMES pour s'assurer qu'ils ressortent et vous pouvez faire attention à les nommer pour éviter les affrontements. Si vous n'utilisez pas inutilement des macros, vous n'avez pas à vous en inquiéter (et ne devez pas lire SHOUTY_NAMES).

si vous voulez juste une constante à l'intérieur d'une fonction, vous ne pouvez pas le faire avec une macro, parce que le préprocesseur ne sait pas ce qu'est une fonction ou ce que cela signifie être à l'intérieur d'elle. Pour limiter une macro pour qu'une certaine partie d'un fichier, vous devez #undef nouveau:

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

comparez à la beaucoup plus sensible:

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

pourquoi préféreriez-vous la macro?

Un vrai emplacement de mémoire

une variable de constexpr est une variable donc il existe dans le programme et vous pouvez faire des choses c++ normales comme prendre son adresse et y lier une référence.

ce code a un comportement indéterminé:

#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 temporaire int doit être créé par le compilateur. La référence qui est retournée par std::max pourrait alors faire référence à ce temporaire, qui n'existe pas après la fin de cette déclaration, donc return h accède à une mémoire invalide.

ce problème n'existe tout simplement pas avec une variable appropriée, car il a un emplacement fixe dans la mémoire qui ne va pas loin:

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 hconst int& h mais le problème peut se poser dans des contextes plus subtils.)

conditions de préprocesseur

le seul moment pour préférer une macro est quand vous avez besoin que sa valeur soit comprise par le préprocesseur, pour l'utiliser 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 une variable ici, parce que le préprocesseur ne comprend pas comment se référer aux variables par leur nom. Il ne comprend les choses basiques très basiques comme l'expansion macro et les directives commençant par # (#include et #define et #if).

Si vous voulez une constante cela peut être compris par le préprocesseur alors vous devez utiliser le préprocesseur pour le définir. Si vous voulez une constante pour le code C++ normal, utilisez le 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 préprocesseur:

using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
78
répondu Jonathan Wakely 2017-02-22 10:37:35

en général, vous devez utiliser constexpr chaque fois que vous le pouvez, et macros seulement si aucune autre solution est possible.

Explication:

Macros sont un simple remplacement dans le code, et pour cette raison, il génère généralement des conflits (par exemple windows.h max macro vs std:: max). En outre, une macro qui fonctionne peut facilement être utilisée d'une manière différente qui déclenche alors des erreurs de compilation étranges. (par exemple Q_PROPERTY utilisé sur les membres de la structure)

en Raison de tous ces incertitudes, c'est un bon style de code pour éviter les macros, exactement comme vous évitez gotos habituellement.

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

4
répondu Adrian Maire 2017-02-22 09:55:53

Grande réponse par Jonathon Wakely. Je vous conseille aussi de jeter un coup d'oeil à réponse de jogojapan pour ce qui est de la différence entre const et constexpr avant même d'envisager l'utilisation des macros.

les Macros sont muets, mais dans un bonne. Ostensiblement de nos jours ils sont un build-aid pour quand vous voulez des parties très spécifiques de votre code à compiler seulement en présence de certains paramètres de construction obtenir "définir." Habituellement, tout ce que cela signifie Est de prendre votre macro nom, ou mieux encore, appelons ça un Trigger, et en ajoutant des choses comme, /D:Trigger,-DTrigger, etc. pour les outils de construction utilisés.

bien qu'il y ait beaucoup d'utilisations différentes pour les macros, ce sont les deux que je vois le plus souvent qui ne sont pas mauvaises/pratiques périmées:

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

alors tant que vous pouvez dans les OP's cas atteindre le même objectif de définir un int constexpr et MACRO, il est peu probable que les deux se chevauchent lorsqu'on utilise les conventions modernes. Voici quelques macro-usage commun qui n'a pas encore été éliminé.

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

comme autre exemple de macro-usage, disons que vous avez du matériel à venir à publier, ou peut-être une génération spécifique de celui-ci qui a quelques solutions de travail délicates que 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
1
répondu kayleeFrye_onDeck 2018-09-21 06:06:43