Constexpr与宏

时间:2017-02-22 09:50:20

标签: c++ c++11 macros constexpr

我应该在哪里使用宏?我更喜欢constexpr? Aren他们基本相同吗?

#define MAX_HEIGHT 720

VS

constexpr unsigned int max_height = 720;

3 个答案:

答案 0 :(得分:93)

  

它们基本不一样吗?

没有。绝对不。甚至没有关闭。

除了您的宏是intconstexpr unsignedunsigned这一事实外,还有一些重要的区别,而宏只有一个优势。< / p>

范围

宏由预处理器定义,并且每次发生时都会简单地替换为代码。预处理器 dumb 并且不理解C ++语法或语义。宏忽略名称空间,类或功能块等范围,因此您不能在源文件中使用其他任何名称。对于定义为正确C ++变量的常量,情况并非如此:

#define MAX_HEIGHT 720
constexpr int max_height = 720;

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

拥有一个名为max_height的成员变量是很好的,因为它是一个类成员,因此具有不同的范围,并且与命名空间范围内的变量不同。如果您尝试为成员重用名称MAX_HEIGHT,那么预处理器会将其更改为无法编译的无意义:

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

这就是为什么你必须给宏UGLY_SHOUTY_NAMES以确保它们脱颖而出,你可以小心命名它们以避免冲突。如果您不必要地使用宏,则不必担心(并且不必阅读SHOUTY_NAMES)。

如果你只想在一个函数内部使用一个常量,你不能用宏来做,因为预处理器不知道函数是什么或者它在里面意味着什么。要将宏限制为仅文件的某个部分,您需要再次#undef

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

与更明智的比较:

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

为什么你更喜欢宏观呢?

实际内存位置

constexpr变量是一个变量,所以它实际上存在于程序中,你可以做普通的C ++事情,比如获取它的地址并绑定对它的引用。

此代码具有未定义的行为:

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

问题是MAX_HEIGHT不是变量,因此对std::max的调用必须由编译器创建临时intstd::max返回的引用可能会引用该临时值,该值在该语句结束后不存在,因此return h访问无效内存。

这个问题根本不存在适当的变量,因为它在内存中有一个固定的位置,不会消失:

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

(实际上,您可能会声明int h而不是const int& h,但问题可能会出现在更微妙的背景下。)

预处理器条件

唯一一次选择宏是指您需要预处理器理解其值,以便在#if条件下使用,例如

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

您无法在此处使用变量,因为预处理器无法理解如何通过名称引用变量。它只能理解基本的非常基本的东西,如宏扩展和以#开头的指令(如#include#define#if)。

如果您想要预处理器可以理解的常量,那么您应该使用预处理器来定义它。如果想要普通C ++代码的常量,请使用普通的C ++代码。

以上示例仅用于演示预处理器条件,但即使该代码也可以避免使用预处理器:

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

答案 1 :(得分:5)

一般来说,你应该尽可能使用constexpr,只有在没有其他解决方案时才使用宏。

理性:

宏是代码中的一个简单替代品,因此,它通常会产生冲突(例如,windows.h max macro vs std :: max)。另外,一个有效的宏可能很容易以不同的方式使用,然后触发奇怪的编译错误。 (例如,在结构成员上使用Q_PROPERTY)

由于所有这些不确定因素,避免宏是一种很好的代码风格,就像你通常避免使用它一样。

constexpr是语义定义的,因此产生的问题要少得多。

答案 2 :(得分:2)

Jonathon Wakely的回答很好。我还建议您在考虑使用宏之前,先jogojapan's answer来了解constconstexpr之间的区别。

宏很愚蠢,但是以的方式。如今,当您希望仅在某些构建参数被“定义”的情况下才编译非常特定的代码部分时,它们如今已成为一种构建助手。通常,这意味着要使用您的宏名称,或更妙的是,将其称为Trigger,然后将/D:Trigger-DTrigger等添加到所使用的构建工具中

虽然宏有很多不同的用法,但我经常看到的这两种用法不是坏/过时的做法:

  1. 特定于硬件和平台的代码部分
  2. 详细程度提高

因此,在OP的情况下,虽然可以实现使用constexprMACRO定义int的相同目标,但是在使用现代约定时,两者不太可能重叠。这是一些尚未被淘汰的常见宏用法。

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

作为宏使用的另一个示例,假设您有一些即将发布的硬件,或者它的特定一代可能具有一些其他人不需要的棘手解决方法。我们将此宏定义为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