我应该在哪里使用宏,哪里应该使用constexpr?它们不是基本相同吗?
#define MAX_HEIGHT 720
与
constexpr unsigned int max_height = 720;
我应该在哪里使用宏,哪里应该使用constexpr?它们不是基本相同吗?
#define MAX_HEIGHT 720
与
constexpr unsigned int max_height = 720;
Answers:
它们不是基本相同吗?
不,绝对不会。差远了。
除了您的宏是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
和#define
and #if
)开头的指令。
如果您想要一个可以被预处理器理解的常量,则应该使用预处理器对其进行定义。如果要为普通C ++代码提供常量,请使用普通C ++代码。
上面的示例只是为了演示预处理器的条件,但是即使该代码也可以避免使用预处理器:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
constexpr
变量不必占用存储器中,直到它的地址(指针/参考)被取; 否则,可以完全优化它(我认为可能会有Standardese对此提供保证)。我要强调这一点,以使人们不会继续enum
误用constexpr
不需要存储的琐碎小东西,而继续使用旧的劣等“ hack” 。
int height
将像宏一样遇到问题,因为宏的范围也与函数相关,本质上也是临时的。3.以上注释“ const int&h将延长临时生存期”是正确的。
limit
,问题在于的返回值std::max
。2.是的,这就是为什么它不返回引用。3.错误,请参阅上面的coliru链接。
const int& h = max(x, y);
并按max
该值返回,则将延长其返回值的生存期。不是通过返回类型,而是通过const int&
它绑定。我写的是正确的。
一般而言,应尽可能使用constexpr
宏,只有在没有其他解决方案可行时才使用宏。
宏是代码中的简单替换,因此,它们通常会产生冲突(例如,windows.h max
macro vs std::max
)。此外,可以以不同的方式轻松使用有效的宏,然后触发奇怪的编译错误。(例如,Q_PROPERTY
用于结构构件)
由于所有这些不确定因素,避免使用宏是一种很好的代码风格,就像您通常避免使用goto一样。
constexpr
是在语义上定义的,因此通常产生的问题要少得多。
#if
预处理器实际有用的条件编译。定义常量不是预处理器有用的功能之一,除非该常量必须是宏,因为它在使用的预处理条件中使用#if
。如果该常数用于普通C ++代码(而不是预处理程序指令),则使用普通C ++变量,而不是预处理程序宏。
Jonathon Wakely的很好回答。我还建议您在考虑使用宏之前,先看看jogojapan的答案之间的区别。const
constexpr
宏很傻,但是用一种很好的方式。如今,当您希望仅在某些构建参数被“定义”的情况下才编译非常特定的代码部分时,它们如今已成为一种构建助手。通常情况下,所有的手段是把你的宏名,或者更好,让我们把它叫做Trigger
,并加入一些事情,如,/D:Trigger
,-DTrigger
,等来构建工具被使用。
尽管宏有很多不同的用法,但我最常看到的这两种用法并不是坏/过时的做法:
因此,虽然在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