Constexpr vs macros

Constexpr vs macros

本文关键字:macros vs Constexpr      更新时间:2023-10-16

我应该在哪里更喜欢使用 macros ,我应该在哪里更喜欢 constexpr ?他们基本上不是一样吗?

#define MAX_HEIGHT 720

vs

constexpr unsigned int max_height = 720;

它们基本上不是一样吗?

否。绝对不。

甚至都没有关闭。

除了您的宏是int,而您的constexpr unsignedunsigned,存在重要的区别,宏仅具有一个优势。

范围

宏由预处理器定义,每次发生时,都可以将其替换为代码。预处理器是 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 variable 是一个变量,因此它实际上存在于程序中,您可以执行正常的C 诸如获取其地址并绑定到它的参考。

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

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

问题是MAX_HEIGHT不是变量,因此对于std::max的调用,必须由编译器创建临时int。然后,std::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>;

一般而言,您应随时使用constexpr,并且只有在没有其他解决方案的情况下才能使用宏。

理由:

宏是代码中简单的替代品,因此,它们通常会产生冲突(例如Windows.H max宏与std::max)。此外,可以很容易地以不同的方式使用起作用的宏,从而可以触发奇怪的汇编错误。(例如,在结构成员上使用的Q_PROPERTY

由于所有这些不确定性,避免宏的代码风格是很好的代码样式,就像您通常避免使用gotos一样。

constexpr是语义上定义的,因此通常会产生较少的问题。

乔纳森·沃克利(Jonathon Wakely)的出色答案。我还建议您查看Jogojapan的答案,以了解constconstexpr与CC_26之间的区别,甚至考虑使用宏的用法。

宏是愚蠢的,但以良好的方式。如今,从表面上看,它们是一个建筑物AID,因为您希望代码的非常具体的部分仅在某些构建参数"定义"的情况下进行编译。通常,所有这些是取您的宏名称,或者更好,我们称其为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