c++ Constexpr vs宏

xxls0lw8  于 2023-06-25  发布在  其他
关注(0)|答案(3)|浏览(183)

我应该在哪里使用 macros,在哪里使用 constexpr?它们基本上不是一样的吗?

#define MAX_HEIGHT 720

vs

constexpr unsigned int max_height = 720;
whlutmcx

whlutmcx1#

它们基本上不是一样的吗?
不绝对不行差远了
除了你的宏是一个int和你的constexpr unsigned是一个unsigned之外,还有一些重要的区别,宏只有一个优点。

作用域

宏由预处理器定义,每次出现时都简单地代入代码中。预处理器是,不理解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的调用,编译器必须创建一个临时的intstd::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,但在更微妙的上下文中可能会出现问题。

预处理器条件

只有在预处理器需要理解宏的值时,才需要使用宏,以便在#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>;
b91juud3

b91juud32#

一般来说,您应该在任何可能的时候使用constexpr,只有在没有其他解决方案的情况下才使用宏。

理由:

宏是代码中的一个简单替换,因此,它们经常会产生冲突(例如:windows.h max宏vs std::max)。此外,一个可以正常工作的宏可能很容易被以不同的方式使用,这可能会触发奇怪的编译错误。(例如,结构构件上使用的Q_PROPERTY
由于所有这些不确定性,避免使用宏是一种很好的代码风格,就像通常避免使用goto一样。
constexpr是语义定义的,因此通常产生的问题要少得多。

pbgvytdp

pbgvytdp3#

Jonathon Wakely的精彩回答。我还建议您在考虑使用宏之前,先看看jogojapan's answer,了解constconstexpr之间的区别。
宏是愚蠢的,但在一个好的方式。表面上看,现在它们是一种构建辅助工具,当你希望代码的特定部分只在某些构建参数被“定义”的情况下被编译时。通常,这意味着使用宏名称,或者更好的是,让我们称之为Trigger,并添加类似/D:Trigger-DTrigger等内容。到正在使用的构建工具。
虽然宏有许多不同的用法,但我最常看到的两种做法并不坏/过时:
1.硬件和平台特定代码部分
1.增加详细构建
因此,虽然在OP的情况下,您可以实现用constexprMACRO定义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

相关问题