我应该在哪里使用 macros,在哪里使用 constexpr?它们基本上不是一样的吗?
#define MAX_HEIGHT 720
vs
constexpr unsigned int max_height = 720;
whlutmcx1#
它们基本上不是一样的吗?不绝对不行差远了除了你的宏是一个int和你的constexpr unsigned是一个unsigned之外,还有一些重要的区别,宏只有一个优点。
int
constexpr unsigned
unsigned
宏由预处理器定义,每次出现时都简单地代入代码中。预处理器是哑,不理解C语法或语义。宏会忽略诸如命名空间、类或函数块之类的作用域,因此您不能在源文件中为任何其他内容使用名称。对于一个定义为正确的C变量的常量来说,这是不正确的:
#define MAX_HEIGHT 720 constexpr int max_height = 720; class Window { // ... int max_height; };
有一个名为max_height的数据成员是很好的,因为它是一个类成员,因此具有不同的作用域,并且与名称空间作用域中的数据成员不同。如果你试图重复使用成员的名字MAX_HEIGHT,那么预处理器会把它改成这样的废话,这样就无法编译了:
max_height
MAX_HEIGHT
class Window { // ... int 720; };
这就是为什么你必须给予宏UGLY_SHOUTY_NAMES,以确保他们脱颖而出,你可以小心命名,以避免冲突。如果你没有不必要地使用宏,你就不必担心这个问题(也不必阅读SHOUTY_NAMES)。如果你只是想在函数中加入一个常量,你不能用宏来实现,因为预处理器不知道函数是什么,也不知道函数中的常量意味着什么。要将宏限制在文件的某个部分,您需要再次#undef它:
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访问的内存无效。对于一个合适的变量,这个问题根本不存在,因为它在内存中有一个固定的位置,不会消失:
std::max
return h
int limit(int height) { constexpr int max_height = 720; const int& h = std::max(height, max_height); // ... return h; }
(In在实践中,你可能会声明int h而不是const int& h,但在更微妙的上下文中可能会出现问题。
int h
const int& h
只有在预处理器需要理解宏的值时,才需要使用宏,以便在#if条件下使用,例如。
#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代码。上面的例子只是为了演示预处理器条件,但即使是这样的代码也可以避免使用预处理器:
#
#include
#define
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
b91juud32#
一般来说,您应该在任何可能的时候使用constexpr,只有在没有其他解决方案的情况下才使用宏。
constexpr
宏是代码中的一个简单替换,因此,它们经常会产生冲突(例如:windows.h max宏vs std::max)。此外,一个可以正常工作的宏可能很容易被以不同的方式使用,这可能会触发奇怪的编译错误。(例如,结构构件上使用的Q_PROPERTY)由于所有这些不确定性,避免使用宏是一种很好的代码风格,就像通常避免使用goto一样。constexpr是语义定义的,因此通常产生的问题要少得多。
max
Q_PROPERTY
pbgvytdp3#
Jonathon Wakely的精彩回答。我还建议您在考虑使用宏之前,先看看jogojapan's answer,了解const和constexpr之间的区别。宏是愚蠢的,但在一个好的方式。表面上看,现在它们是一种构建辅助工具,当你希望代码的特定部分只在某些构建参数被“定义”的情况下被编译时。通常,这意味着使用宏名称,或者更好的是,让我们称之为Trigger,并添加类似/D:Trigger,-DTrigger等内容。到正在使用的构建工具。虽然宏有许多不同的用法,但我最常看到的两种做法并不坏/过时:1.硬件和平台特定代码部分1.增加详细构建因此,虽然在OP的情况下,您可以实现用constexpr或MACRO定义int的相同目标,但在使用现代约定时,这两者不太可能重叠。下面是一些尚未被淘汰的常用宏用法。
const
Trigger
/D:Trigger
-DTrigger
MACRO
#if defined VERBOSE || defined DEBUG || defined MSG_ALL // Verbose message-handling code here #endif
作为宏使用的另一个例子,假设您有一些即将发布的硬件,或者可能是具有其他人不需要的一些棘手的解决方案的特定一代。我们将这个宏定义为GEN_3_HW。
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
3条答案
按热度按时间whlutmcx1#
它们基本上不是一样的吗?
不绝对不行差远了
除了你的宏是一个
int
和你的constexpr unsigned
是一个unsigned
之外,还有一些重要的区别,宏只有一个优点。作用域
宏由预处理器定义,每次出现时都简单地代入代码中。预处理器是哑,不理解C语法或语义。宏会忽略诸如命名空间、类或函数块之类的作用域,因此您不能在源文件中为任何其他内容使用名称。对于一个定义为正确的C变量的常量来说,这是不正确的:
有一个名为
max_height
的数据成员是很好的,因为它是一个类成员,因此具有不同的作用域,并且与名称空间作用域中的数据成员不同。如果你试图重复使用成员的名字MAX_HEIGHT
,那么预处理器会把它改成这样的废话,这样就无法编译了:这就是为什么你必须给予宏
UGLY_SHOUTY_NAMES
,以确保他们脱颖而出,你可以小心命名,以避免冲突。如果你没有不必要地使用宏,你就不必担心这个问题(也不必阅读SHOUTY_NAMES
)。如果你只是想在函数中加入一个常量,你不能用宏来实现,因为预处理器不知道函数是什么,也不知道函数中的常量意味着什么。要将宏限制在文件的某个部分,您需要再次
#undef
它:与更明智的相比:
为什么你更喜欢宏观的?
真实的内存位置
一个constexpr变量 * 是一个变量 *,所以它实际上存在于程序中,你可以做正常的C++事情,比如获取它的地址并绑定一个引用。
此代码具有未定义的行为:
问题是
MAX_HEIGHT
不是一个变量,所以对于std::max
的调用,编译器必须创建一个临时的int
。std::max
返回的引用可能会引用那个临时变量,而这个临时变量在该语句结束后就不存在了,因此return h
访问的内存无效。对于一个合适的变量,这个问题根本不存在,因为它在内存中有一个固定的位置,不会消失:
(In在实践中,你可能会声明
int h
而不是const int& h
,但在更微妙的上下文中可能会出现问题。预处理器条件
只有在预处理器需要理解宏的值时,才需要使用宏,以便在
#if
条件下使用,例如。这里不能使用变量,因为预处理器不知道如何通过名称引用变量。它只理解基本的东西,比如宏扩展和以
#
开头的指令(比如#include
和#define
和#if
)。如果你想要一个常量 * 可以被预处理器 * 理解,那么你应该使用预处理器来定义它。如果你想要一个常量用于普通的C代码,那么就使用普通的C代码。
上面的例子只是为了演示预处理器条件,但即使是这样的代码也可以避免使用预处理器:
b91juud32#
一般来说,您应该在任何可能的时候使用
constexpr
,只有在没有其他解决方案的情况下才使用宏。理由:
宏是代码中的一个简单替换,因此,它们经常会产生冲突(例如:windows.h
max
宏vsstd::max
)。此外,一个可以正常工作的宏可能很容易被以不同的方式使用,这可能会触发奇怪的编译错误。(例如,结构构件上使用的Q_PROPERTY
)由于所有这些不确定性,避免使用宏是一种很好的代码风格,就像通常避免使用goto一样。
constexpr
是语义定义的,因此通常产生的问题要少得多。pbgvytdp3#
Jonathon Wakely的精彩回答。我还建议您在考虑使用宏之前,先看看jogojapan's answer,了解
const
和constexpr
之间的区别。宏是愚蠢的,但在一个好的方式。表面上看,现在它们是一种构建辅助工具,当你希望代码的特定部分只在某些构建参数被“定义”的情况下被编译时。通常,这意味着使用宏名称,或者更好的是,让我们称之为
Trigger
,并添加类似/D:Trigger
,-DTrigger
等内容。到正在使用的构建工具。虽然宏有许多不同的用法,但我最常看到的两种做法并不坏/过时:
1.硬件和平台特定代码部分
1.增加详细构建
因此,虽然在OP的情况下,您可以实现用
constexpr
或MACRO
定义int的相同目标,但在使用现代约定时,这两者不太可能重叠。下面是一些尚未被淘汰的常用宏用法。作为宏使用的另一个例子,假设您有一些即将发布的硬件,或者可能是具有其他人不需要的一些棘手的解决方案的特定一代。我们将这个宏定义为
GEN_3_HW
。