Tom*_*one 54 c++ macros constexpr c++11
我应该在哪里使用宏,哪里更喜欢constexpr?它们基本不一样吗?
#define MAX_HEIGHT 720
Run Code Online (Sandbox Code Playgroud)
VS
constexpr unsigned int max_height = 720;
Run Code Online (Sandbox Code Playgroud)
Jon*_*ely 108
它们基本不一样吗?
不,绝对不是.差远了.
除了你的宏是一个int而你的constexpr unsigned是一个unsigned,你有一个重要的区别,宏只有一个优势.
宏由预处理器定义,并且每次发生时都会简单地替换为代码.预处理器很笨,不懂C++语法或语义.宏忽略名称空间,类或功能块等范围,因此您不能在源文件中使用其他任何名称.对于定义为正确的C++变量的常量,情况并非如此:
#define MAX_HEIGHT 720
constexpr int max_height = 720;
class Window {
// ...
int max_height;
};
Run Code Online (Sandbox Code Playgroud)
调用成员变量是很好的,max_height因为它是一个类成员,因此具有不同的作用域,并且与命名空间作用域不同.如果您尝试重用该MAX_HEIGHT成员的名称,那么预处理器会将其更改为无法编译的无意义:
class Window {
// ...
int 720;
};
Run Code Online (Sandbox Code Playgroud)
这就是为什么你必须给宏UGLY_SHOUTY_NAMES以确保它们脱颖而出,你可以小心命名它们以避免冲突.如果不必要地使用宏,则不必担心(并且不必阅读SHOUTY_NAMES).
如果你只想在一个函数内部使用一个常量,你不能用宏来做,因为预处理器不知道函数是什么或它在里面意味着什么.要将宏限制为仅限文件的某个部分,您需要#undef再次使用它:
int limit(int height) {
#define MAX_HEIGHT 720
return std::max(height, MAX_HEIGHT);
#undef MAX_HEIGHT
}
Run Code Online (Sandbox Code Playgroud)
与更明智的比较:
int limit(int height) {
constexpr int max_height = 720;
return std::max(height, max_height);
}
Run Code Online (Sandbox Code Playgroud)
为什么你更喜欢宏观?
constexpr变量是一个变量,因此它实际存在于程序中,您可以执行普通的C++操作,例如获取其地址并绑定对它的引用.
此代码具有未定义的行为:
#define MAX_HEIGHT 720
int limit(int height) {
const int& h = std::max(height, MAX_HEIGHT);
// ...
return h;
}
Run Code Online (Sandbox Code Playgroud)
问题是它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;
}
Run Code Online (Sandbox Code Playgroud)
(在实践中,你可能会声明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
Run Code Online (Sandbox Code Playgroud)
您无法在此处使用变量,因为预处理器无法理解如何通过名称引用变量.它只能理解基本的非常基本的东西,比如宏扩展和以#(#include和#define和#if)开头的指令.
如果您想要预处理器可以理解的常量,那么您应该使用预处理器来定义它.如果您想要普通C++代码的常量,请使用普通的C++代码.
上面的示例只是为了演示预处理器条件,但即使该代码也可以避免使用预处理器:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
Run Code Online (Sandbox Code Playgroud)
一般而言,应尽可能使用constexpr宏,只有在没有其他解决方案时才使用宏。
宏是代码中的简单替换,因此,它们通常会产生冲突(例如,windows.h maxmacro vs std::max)。此外,可以以不同的方式轻松使用有效的宏,然后触发奇怪的编译错误。(例如,Q_PROPERTY用于结构构件)
由于所有这些不确定因素,避免使用宏是一种很好的代码风格,就像您通常避免使用goto一样。
constexpr 是在语义上定义的,因此通常产生的问题要少得多。
Jonathon Wakely 的精彩回答。我还建议你看看jogojapan的回答,以区别是什么之间const和constexpr你甚至去考虑宏的用法了。
宏是愚蠢的,但以一种很好的方式。现在表面上看,当您希望代码的非常特定部分仅在某些构建参数被“定义”时才编译时,它们是构建辅助工具。通常,这意味着使用您的宏名称,或者更好的是,我们称其为 a Trigger,并在正在使用的构建工具中添加诸如/D:Trigger、-DTrigger、 等内容。
虽然宏有许多不同的用途,但我最常看到的两种是不错的/过时的做法:
因此,虽然您可以在 OP 的情况下实现定义 int withconstexpr或 a的相同目标MACRO,但在使用现代约定时,两者不太可能重叠。以下是一些尚未逐步淘汰的常见宏用法。
#if defined VERBOSE || defined DEBUG || defined MSG_ALL
// Verbose message-handling code here
#endif
Run Code Online (Sandbox Code Playgroud)
作为宏使用的另一个示例,假设您有一些即将发布的硬件,或者它的特定一代有一些其他人不需要的棘手解决方法。我们将这个宏定义为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
Run Code Online (Sandbox Code Playgroud)