Consexpr 与宏

Posted

技术标签:

【中文标题】Consexpr 与宏【英文标题】:Constexpr vs macros 【发布时间】:2017-07-12 07:13:21 【问题描述】:

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

#define MAX_HEIGHT 720

constexpr unsigned int max_height = 720;

【问题讨论】:

AFAIK constexpr 提供更多类型安全 简单:constexr,总是这样。 可能会回答你的一些问题***.com/q/4748083/540286 如果宏类似于#define LOG if(logger) loggger->log(),我还能使用 constexpr 吗? 【参考方案1】:

他们不是基本一样吗?

没有。绝对不。甚至没有关闭。

除了您的宏是int 和您的constexpr unsignedunsigned 之外,还有一些重要的区别,并且宏只有一个优势。

范围

宏由预处理器定义,每次出现时都简单地替换到代码中。预处理器是哑巴,不理解 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;

(实际上,您可能会声明 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>;

【讨论】:

@TobySpeight 不,这两个方面都错了。您无法int&amp; 绑定到结果,因为它返回const int&amp;,因此无法编译。而且它不会延长生命周期,因为您没有将引用直接绑定到临时对象。见coliru.stacked-crooked.com/a/873862de9cd8c175 constexpr 变量在获取其地址(指针/引用)之前不需要占用内存;否则,它可以被完全优化掉(我认为可能有 Standardese 可以保证这一点)。我想强调这一点,这样人们就不会继续使用旧的、劣质的“enum hack”,因为他们认为不需要存储的琐碎constexpr 仍然会占用一些空间。 您的“真实内存位置”部分是错误的: 1. 您按值 (int) 返回,因此复制了一份,临时的不是问题。 2. 如果您通过引用返回 (int&),那么您的 int height 将与宏一样有问题,因为它的范围与函数相关,本质上也是临时的。 3. 上面的注释“const int& h will extend the lifetime of the temporary”是正确的。 @underscore_d true,但这不会改变论点。该变量不需要存储,除非有一个 odr-use 使用它。关键是当需要一个带存储的实变量时,constexpr 变量做正确的事。 @PoweredByRice 叹息,你真的不需要向我解释 C++ 是如何工作的。如果您有 const int&amp; h = max(x, y);max 按值返回,则其返回值的生命周期会延长。不是通过返回类型,而是通过const int&amp; 它绑定到。我写的是对的。【参考方案2】:

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

理由:

宏是代码中的简单替换,因此,它们经常会产生冲突(例如 windows.h max 宏 vs std::max)。此外,一个有效的宏可以很容易地以不同的方式使用,然后会触发奇怪的编译错误。 (例如 Q_PROPERTY 用于结构成员)

由于所有这些不确定性,避免使用宏是一种很好的代码风格,就像您通常避免使用 goto 一样。

constexpr 是语义定义的,因此通常产生的问题要少得多。

【讨论】:

什么情况下不能避免使用宏? 使用#if 的条件编译,即预处理器实际上有用的东西。定义一个常量不是预处理器有用的事情之一,除非该常量必须是一个宏,因为它在预处理器条件下使用#if。如果常量用于普通 C++ 代码(不是预处理器指令),则使用普通 C++ 变量,而不是预处理器宏。 除了使用可变参数宏,主要是宏用于编译器开关,但是尝试用 constexpr 替换当前处理实际代码语句的宏语句(如条件、字符串文字开关)是个好主意吗?跨度> 我会说编译器开关也不是一个好主意。但是,我完全理解有时需要它(也需要宏),尤其是在处理跨平台或嵌入式代码时。回答您的问题:如果您已经在处理预处理器,我会使用宏来清楚直观地了解什么是预处理器以及什么是编译时间。我还建议大量评论并使其使用尽可能短和本地化(避免宏散布在 #if 或 100 行左右)。也许例外是典型的 #ifndef 守卫(#pragma once 的标准),这是很好理解的。【参考方案3】:

Jonathon Wakely 的回答很好。我还建议您在考虑使用宏之前先查看jogojapan's answer,了解constconstexpr 之间的区别。

宏是愚蠢的,但以一种的方式。表面上现在,当您希望代码的非常特定部分仅在某些构建参数被“定义”的情况下才被编译时,它们是一种构建辅助工具。通常,这意味着使用您的宏名称,或者更好的是,我们将其称为Trigger,并将/D:Trigger-DTrigger 等内容添加到正在使用的构建工具中。

虽然宏有许多不同的用途,但这是我最常看到的两种不错/过时的做法:

    硬件和平台特定的代码部分 增加了详细的构建

因此,虽然您可以在 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

【讨论】:

以上是关于Consexpr 与宏的主要内容,如果未能解决你的问题,请参考以下文章

Consexpr 替代放置 new 以使内存中的对象未初始化?

C之函数与宏(四十)

第46课 函数与宏分析

函数与宏定义

微任务与宏任务

第六章 实验报告(函数与宏定义)