是什么使c++预处理器宏成为公认的开发工具?

What would make C++ preprocessor macros an accepted development tool?

本文关键字:开发工具 c++ 预处理 处理器 是什么      更新时间:2023-10-16

显然c++中的预处理器宏是

c++社区对

的恐惧和回避是有理由的。

然而,在一些情况下c++宏是有用的。

看到预处理器宏是非常有用的,可以以非常直接的方式减少重复的代码——

给我留下了一个问题,究竟是什么使预处理器宏"邪恶",或者,正如问题标题所说,需要预处理器宏的哪些功能(或删除功能)才能使它们成为有用的"好"开发工具(而不是每个人在使用它时都感到羞耻的填充)。(毕竟,Lisp语言似乎支持宏。)

请注意:这是不是关于#include#pragma#ifdef。这是关于#define MY_MACRO(...) ...

注意:我不想让这个问题变得主观。如果您认为它是,请随意投票将其移至programmers.SE。

宏被广泛认为是邪恶的,因为预处理器是一个愚蠢的文本替换工具,几乎没有C/c++知识。

在c++ FAQ Lite中可以找到为什么宏是邪恶的四个很好的理由。

在可能的情况下,模板和内联函数是更好的选择。我能想到的c++仍然需要预处理器的唯一原因是#include s和注释删除。

一个广泛争议的优点是使用它来减少代码重复;但是正如您在boost预处理器库中看到的那样,必须花费大量精力来滥用预处理器来处理简单的逻辑,例如循环,从而导致丑陋的语法。在我看来,用真正的高级编程语言编写脚本来生成代码是一个更好的主意,而不是使用预处理器。

引用Paul Mensonides(Boost。预处理器库):

几乎所有与误用预处理器相关的问题源于试图使类对象宏看起来像常量变量和类函数宏调用看起来像底层语言的函数调用。在最好的情况下,类函数宏调用和函数调用之间的相关性应该是偶然的。它永远不应该被视为一个目标。那是一种根本破碎的心态。

由于预处理器被很好地集成到c++中,因此很容易模糊这条线,并且大多数人看不出有什么区别。例如,让某人写一个宏来将两个数字相加,大多数人会这样写:

#define ADD(x, y) ((x) + (y))

这是完全错误的。通过预处理器运行:

#define ADD(x, y) ((x) + (y))
ADD(1, 2) // outputs ((1) + (2))

但答案应该是3,因为1加2等于3。然而,取而代之的是编写一个宏来生成c++表达式。不仅如此,它可以被认为是一个c++函数,但它不是。这就是它导致虐待的地方。它只是生成一个c++表达式,而函数是一个更好的方法。

此外,宏根本不像函数那样工作。预处理器通过扫描和展开宏的过程来工作,这与使用调用堆栈来调用函数有很大的不同。

有时候用宏生成c++代码是可以接受的,只要它不模糊线条。就像使用python作为预处理器来生成代码一样,预处理器也可以做同样的事情,而且它的优点是不需要额外的构建步骤。

同样,预处理器可以与dsl一起使用,比如这里和这里,但是这些dsl在预处理器中有一个预定义的语法,它使用该语法来生成c++代码。它并没有真正模糊界限,因为它使用了不同的语法。

宏有一个值得注意的特性——它们很容易被滥用,而且很难调试。你可以用宏写任何东西,然后把宏扩展成一行代码,当什么都不能工作时,你就很难调试结果代码了。

这个特性本身就会让人思考是否以及如何使用宏来完成任务。

不要忘记,宏在实际编译之前是展开的,所以它们会自动忽略命名空间、作用域、类型安全和许多其他的东西。

宏最重要的一点是它们没有作用域,也不关心上下文。它们几乎是一个转储文本替换工具。所以当你#define max(....然后所有有最大值的地方都被替换了;因此,如果有人在头文件中添加了过于通用的宏名,他们往往会影响到他们不想影响的代码。

另一件事是,如果不小心使用,它们会导致代码很难阅读,因为没有人能很容易地看到宏的计算结果,特别是当多个宏嵌套时。

一个好的准则是选择唯一的名称,并且在生成样板代码时,要尽快#undef它们,以免污染命名空间。

此外,它们不提供类型安全或重载。

有时候宏被认为是生成样板代码的好工具,比如在boost的帮助下。你可以创建一个宏来帮助你创建像 这样的枚举:
ENUM(xenum,(a,b,(c,7)));

可以展开为

enum xenum { a, b, c=7 };
std::string to_string( xenum x ) { .... }

需要在NDEBUG上做出反应的assert()之类的东西通常也更容易作为宏来实现

C开发人员使用宏而c++开发人员使用模板的情况有很多。

显然在一些特殊情况下它们是有用的,但大多数情况下,它是C世界的坏习惯,被那些相信有一种叫做C/c++的语言的人应用到c++中。

所以说"这是邪恶的"比冒着开发人员滥用它们的风险更容易。

  1. 宏不提供类型安全
  2. 参数执行两次的问题,例如#define MAX(a,b) ((a)>(b) ?(a): (b)),并应用于MAX(i++, y——)
  3. 在符号表中不会出现调试问题。

强制程序员为宏使用正确的命名…更好的工具来跟踪宏的替换将解决我的大部分问题。到目前为止,我还不能说我有什么大问题……这是一种让你灼伤自己的东西,之后你要学会特别小心。但它们迫切需要与ide、调试器更好地集成。