我应该在哪里使用宏?我更喜欢constexpr? Aren他们基本相同吗?
#define MAX_HEIGHT 720
VS
constexpr unsigned int max_height = 720;
答案 0 :(得分:93)
它们基本不一样吗?
没有。绝对不。甚至没有关闭。
除了您的宏是int
且constexpr unsigned
是unsigned
这一事实外,还有一些重要的区别,而宏只有一个优势。< / p>
宏由预处理器定义,并且每次发生时都会简单地替换为代码。预处理器 dumb 并且不理解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
您无法在此处使用变量,因为预处理器无法理解如何通过名称引用变量。它只能理解基本的非常基本的东西,如宏扩展和以#
开头的指令(如#include
和#define
和#if
)。
如果您想要预处理器可以理解的常量,那么您应该使用预处理器来定义它。如果想要普通C ++代码的常量,请使用普通的C ++代码。
以上示例仅用于演示预处理器条件,但即使该代码也可以避免使用预处理器:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
答案 1 :(得分:5)
一般来说,你应该尽可能使用constexpr,只有在没有其他解决方案时才使用宏。
理性:
宏是代码中的一个简单替代品,因此,它通常会产生冲突(例如,windows.h max macro vs std :: max)。另外,一个有效的宏可能很容易以不同的方式使用,然后触发奇怪的编译错误。 (例如,在结构成员上使用Q_PROPERTY)
由于所有这些不确定因素,避免宏是一种很好的代码风格,就像你通常避免使用它一样。
constexpr是语义定义的,因此产生的问题要少得多。
答案 2 :(得分:2)
Jonathon Wakely的回答很好。我还建议您在考虑使用宏之前,先jogojapan's answer来了解const
和constexpr
之间的区别。
宏很愚蠢,但是以好的方式。如今,当您希望仅在某些构建参数被“定义”的情况下才编译非常特定的代码部分时,它们如今已成为一种构建助手。通常,这意味着要使用您的宏名称,或更妙的是,将其称为Trigger
,然后将/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