首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Constexpr对宏

Constexpr对宏
EN

Stack Overflow用户
提问于 2017-02-22 09:50:19
回答 3查看 63.8K关注 0票数 134

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

代码语言:javascript
复制
#define MAX_HEIGHT 720

vs

代码语言:javascript
复制
constexpr unsigned int max_height = 720;
EN

回答 3

Stack Overflow用户

发布于 2017-02-22 10:30:49

它们不是基本一样吗?

不是的。绝对不是。一点也不接近。

除了宏是intconstexpr unsignedunsigned这一事实之外,还有一些重要的区别,宏只有one的优势。

作用域

宏由预处理器定义,每次发生时都被简单地替换到代码中。预处理器是,不理解C++语法或语义。宏忽略名称空间、类或函数块等作用域,因此您不能在源文件中使用任何其他名称。对于定义为适当的C++变量的常量来说,情况并非如此:

代码语言:javascript
复制
#define MAX_HEIGHT 720
constexpr int max_height = 720;

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

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

代码语言:javascript
复制
class Window {
  // ...
  int 720;
};

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

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

代码语言:javascript
复制
int limit(int height) {
#define MAX_HEIGHT 720
  return std::max(height, MAX_HEIGHT);
#undef MAX_HEIGHT
}

与那些更明智的人相比:

代码语言:javascript
复制
int limit(int height) {
  constexpr int max_height = 720;
  return std::max(height, max_height);
}

你为什么更喜欢宏呢?

一个真实的内存位置

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

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

代码语言:javascript
复制
#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访问无效内存。

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

代码语言:javascript
复制
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条件下使用。

代码语言:javascript
复制
#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++代码。

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

代码语言:javascript
复制
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
票数 197
EN

Stack Overflow用户

发布于 2017-02-22 09:55:53

一般来说,您应该在可能的时候使用constexpr,只有在没有其他解决方案的情况下才使用宏。

理由:

宏在代码中是一个简单的替代品,由于这个原因,它们经常产生冲突(例如,windows.h max宏与std::max)。此外,一个可以工作的宏可以很容易地以一种不同的方式使用,从而触发奇怪的编译错误。(例如用于结构成员的Q_PROPERTY )

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

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

票数 17
EN

Stack Overflow用户

发布于 2018-09-21 06:06:43

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

宏是愚蠢的,但在一个好的方面。从表面上看,它们现在是一种构建工具,当您希望代码中非常特定的部分只在特定的构建参数得到“定义”的情况下编译时。通常,这意味着使用宏名称,或者更好的是,我们将其命名为Trigger,并将诸如/D:Trigger-DTrigger等添加到正在使用的构建工具中。

虽然宏有许多不同的用途,但我经常看到的这两种方法并不坏/过时:

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

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

代码语言:javascript
复制
#if defined VERBOSE || defined DEBUG || defined MSG_ALL
    // Verbose message-handling code here
#endif

作为宏使用的另一个例子,让我们假设您有一些即将发布的硬件,或者可能有一些其他人不需要的棘手的解决方案。我们将把这个宏定义为GEN_3_HW

代码语言:javascript
复制
#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
票数 11
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42388077

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档