我应该选择在哪里使用宏,在哪里我应该更喜欢constexpr?它们不是基本一样吗?
#define MAX_HEIGHT 720vs
constexpr unsigned int max_height = 720;发布于 2017-02-22 10:30:49
它们不是基本一样吗?
不是的。绝对不是。一点也不接近。
除了宏是int和constexpr unsigned是unsigned这一事实之外,还有一些重要的区别,宏只有one的优势。
作用域
宏由预处理器定义,每次发生时都被简单地替换到代码中。预处理器是哑,不理解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>;发布于 2017-02-22 09:55:53
一般来说,您应该在可能的时候使用constexpr,只有在没有其他解决方案的情况下才使用宏。
理由:
宏在代码中是一个简单的替代品,由于这个原因,它们经常产生冲突(例如,windows.h max宏与std::max)。此外,一个可以工作的宏可以很容易地以一种不同的方式使用,从而触发奇怪的编译错误。(例如用于结构成员的Q_PROPERTY )
由于所有这些不确定因素,避免宏是很好的代码风格,就像通常避免gotos一样。
constexpr是语义定义的,因此通常生成的问题要少得多。
发布于 2018-09-21 06:06:43
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
#endifhttps://stackoverflow.com/questions/42388077
复制相似问题