134 votes

Constexpr vs macros

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

#define MAX_HEIGHT 720

vs

constexpr unsigned int max_height = 720;

5 votes

AFAIK constexpr fournit plus de sécurité de type

24 votes

Facile : constexr, toujours.

0 votes

Il pourrait répondre à certaines de vos questions stackoverflow.com/q/4748083/540286

197voto

Jonathan Wakely Points 45593

Ne sont-ils pas fondamentalement les mêmes ?

Non. Absolument pas. Il s'en faut de peu.

Outre le fait que votre macro est un int et votre constexpr unsigned est un unsigned Il y a des différences importantes et les macros n'ont qu'une seule fonction, celle d'aider à la mise en place d'un système d'information. un avantage.

Champ d'application

Une macro est définie par le préprocesseur et est simplement substituée dans le code chaque fois qu'elle apparaît. Le préprocesseur est muet et ne comprend pas la syntaxe ou la sémantique du C++. Les macros ignorent les champs d'application tels 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 le cas pour une constante définie comme une véritable variable C++ :

#define MAX_HEIGHT 720
constexpr int max_height = 720;

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

Il est tout à fait possible d'avoir une variable 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 qu'il est distinct de celui de l'espace de noms. Si vous essayez de réutiliser le nom MAX_HEIGHT pour le membre, le préprocesseur le changerait en ce non-sens qui ne compilerait pas :

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

C'est pourquoi il faut donner des macros UGLY_SHOUTY_NAMES pour qu'elles se distinguent et vous pouvez faire attention à leur nom pour éviter les conflits. Si vous n'utilisez pas de macros inutilement, vous n'avez pas à vous soucier de cela (et vous n'avez pas à lire la section SHOUTY_NAMES ).

Si vous voulez simplement 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 signifie être à l'intérieur d'une fonction. Pour limiter une macro à une certaine partie d'un fichier, il faut #undef encore une fois :

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

A comparer avec le bien plus raisonnable :

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

Pourquoi préférez-vous la macro ?

Un emplacement de mémoire réel

Une variable constexpr est une variable afin qu'il existe réellement dans le programme et que vous puissiez faire des choses normales en C++, comme prendre son adresse et lier une référence à lui.

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

Ce problème n'existe tout simplement pas avec une variable propre, parce qu'elle a un emplacement fixe dans la 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 pas const int& h mais le problème peut se poser dans des contextes plus subtils).

Conditions du préprocesseur

Le seul cas où il convient de préférer une macro est lorsque sa valeur doit être comprise par le préprocesseur, par exemple dans les cas suivants #if conditions, par exemple

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

Il n'est pas possible d'utiliser une variable ici, car le préprocesseur ne sait pas comment désigner les variables par leur nom. Il ne comprend que des choses très basiques comme l'expansion des macros et les directives commençant par # (comme #include y #define y #if ).

Si vous souhaitez un qui peut être compris par le préprocesseur vous devez utiliser le préprocesseur pour le définir. Si vous voulez une constante pour du code C++ normal, utilisez du code C++ normal.

L'exemple ci-dessus est juste pour démontrer une condition du 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>;

0 votes

const int& h prolongera la durée de vie du temporaire. Si vous aviez utilisé un simple int& h vous auriez eu raison.

2 votes

@TobySpeight non, c'est faux sur les deux points. Vous ne peut lier int& au résultat, car il renvoie const int& de sorte qu'il ne sera pas compilé. De plus, la durée de vie n'est pas prolongée, car la référence n'est pas liée directement à l'élément temporaire. Voir coliru.stacked-crooked.com/a/873862de9cd8c175

1 votes

17voto

Adrian Maire Points 6604

D'une manière générale, il convient d'utiliser constexpr chaque fois que vous le pouvez, et les macros seulement si aucune autre solution n'est possible.

Raison d'être :

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 ). En outre, une macro qui fonctionne peut facilement être utilisée d'une manière différente, ce qui peut déclencher d'étranges erreurs de compilation. (par ex. Q_PROPERTY utilisé sur les éléments de la structure)

En raison de toutes ces incertitudes, il est de bon ton d'éviter les macros, de la même manière que l'on évite les gotos.

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

1 votes

Dans quel cas l'utilisation d'une macro est-elle inévitable ?

5 votes

Compilation conditionnelle à l'aide de #if c'est-à-dire les choses pour lesquelles le préprocesseur est réellement utile. Définir une constante ne fait pas partie des choses pour lesquelles le préprocesseur est utile, sauf si cette constante doit est une macro parce qu'elle est utilisée dans les conditions du préprocesseur à l'aide de #if . Si la constante est destinée à être utilisée dans du code C++ normal (et non dans des directives du préprocesseur), il convient d'utiliser une variable C++ normale, et non une macro du préprocesseur.

0 votes

A l'exception de l'utilisation des macros variadiques, la plupart des macros sont utilisées pour les commutations du compilateur, mais essayer de remplacer les instructions macro actuelles (telles que les commutations conditionnelles, les commutations de chaînes littérales) traitant des instructions de code réelles par constexpr est une bonne idée ?

11voto

kayleeFrye_onDeck Points 3244

Excellente réponse de Jonathon Wakely . Je vous conseille également de jeter un coup d'œil à Réponse de jogojapan sur la différence entre const y constexpr avant même d'envisager l'utilisation de macros.

Les macros sont stupides, mais dans un bon manière. Ostensiblement, il s'agit aujourd'hui d'une aide à la compilation lorsque vous souhaitez que des parties très spécifiques de votre code ne soient compilées qu'en présence de certains paramètres de compilation "définis". Habituellement, tout ce que cela signifie est de prendre le nom de votre macro, ou mieux encore, de l'appeler un Trigger et en ajoutant des éléments tels que, /D:Trigger , -DTrigger etc. aux outils de construction utilisés.

Bien qu'il existe de nombreuses utilisations différentes des macros, voici les deux que je vois le plus souvent et qui ne sont pas de mauvaises pratiques ou des pratiques dépassées :

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

Ainsi, dans le cas de l'OP, il est possible d'atteindre le même objectif en définissant un int à l'aide de constexpr ou un MACRO il est peu probable que les deux se chevauchent si l'on utilise les conventions modernes. Voici quelques utilisations courantes de macros qui n'ont pas encore été supprimées.

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

Autre exemple d'utilisation des macros : supposons que vous ayez un matériel à sortir prochainement, ou peut-être une génération spécifique de ce matériel qui présente des solutions de contournement délicates que les autres n'ont pas besoin de connaître. Nous définirons cette macro comme suit 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

0 votes

Cette deuxième option est mauvaise pratique, et devrait être remplacée par if constexpr qui invoquent les constexpr qui renvoient leurs valeurs macro respectives. La raison en est que vous voulez toujours que le compilateur teste que vos changements ne vont pas casser la compilation sur d'autres plateformes que celle sur laquelle vous travaillez. L'exception à cette règle est, bien sûr, lorsque les inclusions et les fonctions que vous utilisez sont complètement différentes sur les différentes plateformes, et que vous n'avez tout simplement pas les mêmes fonctions à appeler.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X