Constexpr与宏


Answers:


146

它们不是基本相同吗?

不,绝对不会。差远了。

除了您的宏是anint和您constexpr unsigned是an之外unsigned,还有一些重要的区别,并且宏仅具有一个优点。

范围

宏由预处理器定义,并且每次发生时都被替换为代码。预处理器很笨,不能理解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临时文件的调用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

您不能在此处使用变量,因为预处理器无法理解如何按名称引用变量。它仅了解基本的非常基本的内容,例如宏扩展和以#(like#include#defineand #if)开头的指令。

如果您想要一个可以被预处理器理解的常量则应该使用预处理器对其进行定义。如果要为普通C ++代码提供常量,请使用普通C ++代码。

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

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

3
constexpr变量不必占用存储器中,直到它的地址(指针/参考)被取; 否则,可以完全优化它(我认为可能会有Standardese对此提供保证)。我要强调这一点,以使人们不会继续enum误用constexpr不需要存储的琐碎小东西,而继续使用旧的劣等“ hack” 。
underscore_d

3
您的“实际内存位置”部分是错误的:1.您正在按值(int)返回,因此进行了复制,临时不是问题。2.如果您是通过引用(int&)返回的,则您int height将像宏一样遇到问题,因为宏的范围也与函数相关,本质上也是临时的。3.以上注释“ const int&h将延长临时生存期”是正确的。
PoweredByRice

4
@underscore_d是,但这不会更改参数。除非对该变量进行了多次使用,否则不需要存储该变量。关键是,当需要带存储的实型变量时,constexpr变量会做正确的事情。
Jonathan Wakely'2

1
@PoweredByRice 1.问题与的返回值无关limit,问题在于的返回值std::max。2.是的,这就是为什么它不返回引用。3.错误,请参阅上面的coliru链接。
Jonathan Wakely'2

3
@PoweredByRice感叹,您真的不需要向我解释C ++的工作原理。如果您有const int& h = max(x, y);并按max该值返回,则将延长其返回值的生存期。不是通过返回类型,而是通过const int&它绑定。我写的是正确的。
Jonathan Wakely

11

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

理由:

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

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

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


1
在什么情况下不可避免使用宏?
Tom Dorone

3
使用#if预处理器实际有用的条件编译。定义常量不是预处理器有用的功能之一,除非该常量必须是宏,因为它在使用的预处理条件中使用#if。如果该常数用于普通C ++代码(而不是预处理程序指令),则使用普通C ++变量,而不是预处理程序宏。
Jonathan Wakely'2

除了使用可变参数宏之外,大多数宏都用于编译器开关,但是尝试使用constexpr替换处理实际代码语句的当前宏语句(例如条件字符串字符串开关)是一个好主意吗?

我会说编译器开关也不是一个好主意。但是,我完全理解它有时是必需的(也是宏),尤其是处理跨平台或嵌入式代码时。要回答您的问题:如果您已经在使用预处理器,那么我将使用宏来使您清楚,直观地了解什么是预处理器以及什么是编译时间。我还建议大量评论,并使其用法尽可能短和局部(避免宏散布在#if周围或100行)。也许例外是众所周知的典型#ifndef防护(一次#pragma的标准)。
阿德里安·梅尔

3

Jonathon Wakely的很好回答。我还建议您在考虑使用宏之前,先看看jogojapan的答案之间的区别。constconstexpr

宏很傻,但是用一种很好的方式。如今,当您希望仅在某些构建参数被“定义”的情况下才编译非常特定的代码部分时,它们如今已成为一种构建助手。通常情况下,所有的手段是把你的宏名,或者更好,让我们把它叫做Trigger,并加入一些事情,如,/D:Trigger-DTrigger,等来构建工具被使用。

尽管宏有很多不同的用法,但我最常看到的这两种用法并不是坏/过时的做法:

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

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

#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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.