Constexpr与宏

Tom*_*one 54 c++ macros constexpr c++11

我应该在哪里使用宏,哪里更喜欢constexpr?它们基本不一样吗?

#define MAX_HEIGHT 720
Run Code Online (Sandbox Code Playgroud)

VS

constexpr unsigned int max_height = 720;
Run Code Online (Sandbox Code Playgroud)

Jon*_*ely 108

它们基本不一样吗?

不,绝对不是.差远了.

除了你的宏是一个int而你的constexpr unsigned是一个unsigned,你有一个重要的区别,宏只有一个优势.

范围

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

#define MAX_HEIGHT 720
constexpr int max_height = 720;

class Window {
  // ...
  int max_height;
};
Run Code Online (Sandbox Code Playgroud)

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

class Window {
  // ...
  int 720;
};
Run Code Online (Sandbox Code Playgroud)

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

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

int limit(int height) {
#define MAX_HEIGHT 720
  return std::max(height, MAX_HEIGHT);
#undef MAX_HEIGHT
}
Run Code Online (Sandbox Code Playgroud)

与更明智的比较:

int limit(int height) {
  constexpr int max_height = 720;
  return std::max(height, max_height);
}
Run Code Online (Sandbox Code Playgroud)

为什么你更喜欢宏观?

一个真实的内存位置

constexpr变量是一个变量,因此它实际存在于程序中,您可以执行普通的C++操作,例如获取其地址并绑定对它的引用.

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

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

问题是它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;
}
Run Code Online (Sandbox Code Playgroud)

(在实践中,你可能会声明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
Run Code Online (Sandbox Code Playgroud)

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

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

上面的示例只是为了演示预处理器条件,但即使该代码也可以避免使用预处理器:

using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
Run Code Online (Sandbox Code Playgroud)

  • `constexpr` 变量在其地址(指针/引用)被获取之前不需要占用内存;否则,它可以完全优化掉(我认为可能有 Standardese 可以保证这一点)。我想强调这一点,这样人们就不会继续使用旧的、低劣的“enum”hack,因为它被误导了,认为不需要存储的琐碎“constexpr”仍然会占用一些空间。 (3认同)
  • 您的“真实内存位置”部分是错误的: 1. 您是按值 (int) 返回的,因此制作了副本,临时没有问题。2. 如果您通过引用返回 (int&amp;),那么您的 `int height` 将与宏一样有问题,因为它的作用域与函数相关,本质上也是临时的。3. 上面的评论,“const int&amp;h 将延长临时的生命周期”是正确的。 (3认同)
  • @PoweredByRice 叹息,你真的不需要向我解释 C++ 是如何工作的。如果你有 `const int&amp; h = max(x, y);` 并且 `max` 按值返回,则其返回值的生命周期会延长。不是通过返回类型,而是通过它所绑定的 `const int&amp;`。我写的是正确的。 (3认同)
  • @TobySpeight 不,这两点都是错误的。你_不能_将`int&amp;`绑定到结果,因为它返回`const int&amp;`所以它不会编译。而且它不会延长生命周期,因为您没有将引用直接绑定到临时对象。请参阅http://coliru.stacked-crooked.com/a/873862de9cd8c175 (2认同)
  • @underscore_d是,但这不会更改参数。该变量不需要存储,除非对其进行了多次使用。关键是,当需要带存储的实型变量时,constexpr变量会做正确的事情。 (2认同)

Adr*_*ire 7

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

理由:

宏是代码中的简单替换,因此,它们通常会产生冲突(例如,windows.h maxmacro vs std::max)。此外,可以以不同的方式轻松使用有效的宏,然后触发奇怪的编译错误。(例如,Q_PROPERTY用于结构构件)

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

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

  • 使用#if条件编译,即预处理器实际上有用的东西。定义常量不是预处理器有用的功能之一,除非常量_must_是一个宏,因为它在使用#if的预处理器条件中使用。如果常量用于普通C ++代码(而不是预处理程序指令),则使用普通C ++变量,而不是预处理程序宏。 (2认同)

kay*_*eck 7

Jonathon Wakely 的精彩回答。我还建议你看看jogojapan的回答,以区别是什么之间constconstexpr你甚至去考虑宏的用法了。

宏是愚蠢的,但以一种很好的方式。现在表面上看,当您希望代码的非常特定部分仅在某些构建参数被“定义”时才编译时,它们是构建辅助工具。通常,这意味着使用您的宏名称,或者更好的是,我们称其为 a Trigger,并在正在使用的构建工具中添加诸如/D:Trigger-DTrigger、 等内容。

虽然宏有许多不同的用途,但我最常看到的两种是不错的/过时的做法:

  1. 硬件和平台特定的代码部分
  2. 增加冗长的构建

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

#if defined VERBOSE || defined DEBUG || defined MSG_ALL
    // Verbose message-handling code here
#endif
Run Code Online (Sandbox Code Playgroud)

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