C++ Constexpr 与宏

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42388077/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 15:29:42  来源:igfitidea点击:

Constexpr vs macros

c++c++11macrosconstexpr

提问by Tom Dorone

Where should I prefer using macrosand where should I prefer constexpr? Aren't they basically the same?

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

#define MAX_HEIGHT 720

vs

对比

constexpr unsigned int max_height = 720;

回答by Jonathan Wakely

Aren't they basically the same?

他们不是基本一样吗?

No. Absolutely not. Not even close.

不。绝对不是。差远了。

Apart from the fact your macro is an intand your constexpr unsignedis an unsigned, there are important differences and macros only have oneadvantage.

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

Scope

范围

A macro is defined by the preprocessor and is simply substituted into the code every time it occurs. The preprocessor is dumband doesn't understand C++ syntax or semantics. Macros ignore scopes such as namespaces, classes or function blocks, so you can't use a name for anything else in a source file. That's not true for a constant defined as a proper C++ variable:

宏由预处理器定义,每次出现时都会简单地替换到代码中。预处理器很笨,不了解 C++ 语法或语义。宏会忽略名称空间、类或功能块等作用域,因此您不能为源文件中的任何其他内容使用名称。对于定义为适当 C++ 变量的常量而言,情况并非如此:

#define MAX_HEIGHT 720
constexpr int max_height = 720;

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

It's fine to have a member variable called max_heightbecause it's a class member and so has a different scope, and is distinct from the one at namespace scope. If you tried to reuse the name MAX_HEIGHTfor the member then the preprocessor would change it to this nonsense that wouldn't compile:

可以调用成员变量,max_height因为它是类成员,因此具有不同的作用域,并且与命名空间作用域的作用域不同。如果您尝试重用MAX_HEIGHT成员的名称,则预处理器会将其更改为无法编译的废话:

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

This is why you have to give macros UGLY_SHOUTY_NAMESto ensure they stand out and you can be careful about naming them to avoid clashes. If you don't use macros unnecessarily you don't have to worry about that (and don't have to read SHOUTY_NAMES).

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

If you just want a constant inside a function you can't do that with a macro, because the preprocessor doesn't know what a function is or what it means to be inside it. To limit a macro to only a certain part of a file you need to #undefit again:

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

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

Compare to the far more sensible:

与更明智的相比:

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

Why would you prefer the macro one?

你为什么更喜欢宏呢?

A real memory location

一个真实的内存位置

A constexpr variable is a variableso it actually exists in the program and you can do normal C++ things like take its address and bind a reference to it.

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

This code has undefined behaviour:

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

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

The problem is that MAX_HEIGHTisn't a variable, so for the call to std::maxa temporary intmust be created by the compiler. The reference that is returned by std::maxmight then refer to that temporary, which doesn't exist after the end of that statement, so return haccesses invalid memory.

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

That problem simply doesn't exist with a proper variable, because it has a fixed location in memory that doesn't go away:

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

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

(In practice you'd probably declare int hnot const int& hbut the problem can arise in more subtle contexts.)

(实际上,您可能会声明int h没有,const int& h但问题可能会出现在更微妙的上下文中。)

Preprocessor conditions

预处理器条件

The only time to prefer a macro is when you need its value to be understood by the preprocessor, for use in #ifconditions, e.g.

唯一喜欢宏的时间是当您需要预处理器理解它的值时,以便在#if条件中使用,例如

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

You couldn't use a variable here, because the preprocessor doesn't understand how to refer to variables by name. It only understands basic very basic things like macro expansion and directives beginning with #(like #includeand #defineand #if).

此处不能使用变量,因为预处理器不了解如何按名称引用变量。它只理解基本的非常基本的东西,如宏扩展和以#(如#includeand#define#if)开头的指令。

If you want a constant that can be understood by the preprocessorthen you should use the preprocessor to define it. If you want a constant for normal C++ code, use normal C++ code.

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

The example above is just to demonstrate a preprocessor condition, but even that code could avoid using the preprocessor:

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

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

回答by Adrian Maire

Generally speaking, you should use constexprwhenever you may, and macros only if no other solution is possible.

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

Rationale:

理由:

Macros are a simple replacement in the code, and for this reason, they often generate conflicts (e.g. windows.h maxmacro vs std::max). Additionally, a macro which works may easily be used in a different way which can then trigger strange compilation errors. (e.g. Q_PROPERTYused on structure members)

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

Due to all those uncertainties, it is good code style to avoid macros, exactly like you'd usually avoid gotos.

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

constexpris semantically defined, and thus typically generates far less issues.

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

回答by kayleeFrye_onDeck

Great answer by Jonathon Wakely. I'd also advise you to take a look at jogojapan's answeras to what the difference is between constand constexprbefore you even go about considering the usage of macros.

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

Macros are dumb, but in a goodway. Ostensibly nowadays they're a build-aid for when you want very specific parts of your code to only be compiled in the presence of certain build parameters getting "defined". Usually, all that means is taking your macro name, or better yet, let's call it a Trigger, and adding things like, /D:Trigger, -DTrigger, etc. to the build tools being used.

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

While there's many different uses for macros, these are the two I see most often that aren't bad/out-dated practices:

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

  1. Hardware and Platform-specific code sections
  2. Increased verbosity builds
  1. 硬件和平台特定的代码部分
  2. 增加冗长的构建

So while you can in the OP's case accomplish the same goal of defining an int with constexpror a MACRO, it's unlikely the two will have overlap when using modern conventions. Here's some common macro-usage that hasn't been phased out, yet.

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

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

As another example for macro-usage, let's say you have some upcoming hardware to release, or maybe a specific generation of it that has some tricky workarounds that the others don't require. We'll define this macro as GEN_3_HW.

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