我应该在哪里使用宏,在哪里使用constexpr?它们不是基本上一样吗?
#define MAX_HEIGHT 720
对抗
constexpr unsigned int max_height = 720;
#define MAX_HEIGHT 720
constexpr int max_height = 720;
class Window {
// ...
int max_height;
};
在类成员中使用名为max_height
的数据成员是可以的,因为它具有不同的作用域,并且与命名空间作用域中的变量是不同的。如果您尝试重用成员的名称MAX_HEIGHT
,预处理器会将其更改为无法编译的无意义内容:
class Window {
// ...
int 720;
};
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);
}
#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
在这里你不能使用变量,因为预处理器不知道如何通过名称引用变量。它只能理解基本的东西,比如宏展开和以#
开头的指令(比如#include
、#define
和#if
)。
如果你想要一个可以被预处理器理解的常量,那么你应该使用预处理器来定义它。如果你想要一个用于普通C++代码的常量,就使用普通的C++代码。
上面的例子只是为了演示预处理器条件,但即使那段代码也可以避免使用预处理器:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
int&
绑定到结果上,因为它返回的是 const int&
,所以编译不会通过。而且它也不会延长生命周期,因为你没有直接将引用绑定到临时对象上。请参见 http://coliru.stacked-crooked.com/a/873862de9cd8c175 - Jonathan Wakelyconstexpr
变量在其地址(指针/引用)被使用之前不需要占用内存;否则,它可以完全被优化掉(我认为可能有某些标准规定保证这一点)。我想强调这一点,以便人们不会继续使用旧的、低效的“枚举 hack”,因为他们错误地认为一个不需要存储的平凡 constexpr
仍然会占用一些内存。 - underscore_dint height
与宏定义一样也会成为问题,因为它的作用域与函数绑定,本质上也是临时的。const int& h = max(x, y);
并且max
通过值返回,那么它返回值的生存期会被延长。这种延长不是由返回类型决定的,而是由绑定返回值的const int&
决定的。我写的是正确的。 - Jonathan Wakely一般来说,尽可能使用 constexpr
,仅当没有其他解决方案时才使用宏。
宏在代码中是简单的替换,因此经常会产生冲突(例如,windows.h 的 max
宏与 std::max
)。此外,可行的宏很容易以不同的方式使用,从而触发奇怪的编译错误(例如,在结构成员上使用 Q_PROPERTY
)。
由于所有这些不确定性,好的代码风格是避免使用宏,就像通常避免使用 goto 一样。
constexpr
是语义定义的,因此通常会生成更少的问题。
#if
,也就是预处理器实际有用的东西。定义常量不是预处理器有用的东西之一,除非该常量必须是宏,因为它在预处理条件中使用了 #if
。如果该常量用于普通的 C++ 代码(而不是预处理器指令),则应使用普通的 C++ 变量,而不是预处理器宏。 - Jonathan WakelyJonathon Wakely给出了很好的答案。在考虑使用宏之前,我还建议您查看jogojapan的回答,了解const
和constexpr
之间的区别。
宏是愚蠢的,但以一种好的方式。现在,它们基本上是构建辅助工具,用于当您希望代码的特定部分仅在某些构建参数被“定义”时编译时。通常,这意味着获取您的宏名称,或者更好的是,让我们称其为触发器
,并将类似于/D:Trigger
,-DTrigger
等内容添加到正在使用的构建工具中。
虽然有许多不同的宏用途,但以下是我最常见到的两个不是坏的/过时的做法:
因此,在OP的情况下,尽管您可以使用constexpr
或MACRO
来实现相同的定义int目标,但在使用现代约定时,两者不太可能重叠。以下是一些常见的尚未淘汰的宏用法。
#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
#define LOG if(logger) loggger->log()
,我还可以使用 constexpr 吗? - KcFnMivoid log() { if(logger != nullptr) logger->log(); }
- Pharap