简体   繁体   English

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

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

Apparently the preprocessor macros in C++ are 显然C++ 中的预处理器宏是

justifiably feared and shunned by the C++ community. C++ 社区有理由害怕和回避。

However, there are several cases where C++ macros are beneficial .但是,在某些情况下C++ 宏是有益的

Seeing as preprocessor macros can be extremely useful and can reduce repetitive code in a very straightforward manner --将预处理器宏视为非常有用,并且可以以非常直接的方式减少重复代码——

-- leaves me with the question, what exactly is it that makes preprocessor macros "evil", or, as the question title says, which feature ( or removal of feature ) would be needed from preprocessor macros to make them useful as a "good" development tool (instead of a fill-in that everyone's ashamed of when using it). - 给我留下一个问题,究竟是什么让预处理器宏“邪恶”,或者,正如问题标题所说,预处理器宏需要哪些功能(或删除功能)才能使它们作为“好”有用" 开发工具(而不是一个大家在使用时都害臊的填空题)。 (After all, the Lisp languages seem to embrace macros.) (毕竟,Lisp 语言似乎包含宏。)

Please Note : This is not about #include or #pragma or #ifdef .请注意:这与#include#pragma#ifdef无关 This is about #define MY_MACRO(...)...这是关于#define MY_MACRO(...)...

Note: I do not intend for this question to be subjective.注意:我不打算让这个问题变得主观。 Should you think it is, feel free to vote to move it to programmers.SE.如果您认为是,请随时投票将其移至programmers.SE。

Macros are widely considered evil because the preprocessor is a stupid text replacement tool that has little to none knowledge of C/C++.宏被广泛认为是邪恶的,因为预处理器是一个愚蠢的文本替换工具,几乎没有 C/C++ 知识。

Four very good reasons why macros are evil can be found in the C++ FAQ Lite .C++ FAQ Lite中可以找到为什么宏是邪恶的四个很好的理由。

Where possible, templates and inline functions are a better choice.在可能的情况下,模板和内联函数是更好的选择。 The only reason I can think of why C++ still needs the preprocessor is for #include s and comment removal.我能想到为什么 C++ 仍然需要预处理器的唯一原因是用于#include和注释删除。

A widely disputed advantage is to use it to reduce code repetition;一个广受争议的优势是使用它来减少代码重复; but as you can see by the boost preprocessor library, much effort has to be put to abuse the preprocessor for simple logic such as loops, leading to ugly syntax.但是正如您从 boost 预处理器库中看到的那样,必须付出很多努力来滥用预处理器来处理诸如循环之类的简单逻辑,从而导致语法丑陋。 In my opinion, it is a better idea to write scripts in a real high-level programming language for code generation instead of using the preprocessor.在我看来,用真正的高级编程语言编写脚本来生成代码而不是使用预处理器是一个更好的主意。

Most preprocessor abuse come from misunderstanding, to quote Paul Mensonides(the author of the Boost.Preprocessor library):大多数预处理器滥用来自误解,引用 Paul Mensonides( Boost.Preprocessor库的作者)的话:

Virtually all issues related to the misuse of the preprocessor stems from attempting to make object-like macros look like constant variables and function-like macro invocations look like underlying-language function calls.几乎所有与滥用预处理器有关的问题都源于试图使类对象宏看起来像常量变量,而类函数宏调用看起来像底层语言 function 调用。 At best, the correlation between function-like macro invocations and function calls should be incidental.充其量,类似函数的宏调用和 function 调用之间的相关性应该是偶然的。 It should never be considered to be a goal.永远不应将其视为目标。 That is a fundamentally broken mentality.这是一种从根本上破碎的心态。

As the preprocessor is well integrated into C++, its easier to blur the line, and most people don't see a difference.由于预处理器很好地集成到 C++ 中,因此更容易模糊界限,大多数人看不出有什么区别。 For example, ask someone to write a macro to add two numbers together, most people will write something like this:比如让别人写一个宏把两个数相加,大多数人会这样写:

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

This is completely wrong.这是完全错误的。 Runs this through the preprocessor:通过预处理器运行它:

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

But the answer should be 3, since adding 1 to 2 is 3. Yet instead a macro is written to generate a C++ expression.但答案应该是 3,因为将 1 加到 2 是 3。然而,编写了一个宏来生成 C++ 表达式。 Not only that, it could be thought of as a C++ function, but its not.不仅如此,它可以被认为是 C++ function,但它不是。 This is where it leads to abuse.这就是导致滥用的地方。 Its just generating a C++ expression, and a function is a much better way to go.它只是生成一个 C++ 表达式和一个 function 是 go 的更好方法。

Furthermore, macros don't work like functions at all.此外,宏根本不像函数那样工作。 The preprocessor works through a process of scanning and expanding macros, which is very different than using a call stack to call functions.预处理器通过扫描和扩展宏的过程来工作,这与使用调用堆栈调用函数有很大不同。

There are times it can be acceptable for macros to generate C++ code, as long as it isn't blurring the lines.有时宏生成 C++ 代码是可以接受的,只要它不模糊线条即可。 Just like if you were to use python as a preprocessor to generate code, the preprocessor can do the same, and has the advantage that it doesn't need an extra build step.就像您使用 python 作为预处理器来生成代码一样,预处理器也可以这样做,并且具有不需要额外构建步骤的优点。

Also, the preprocessor can be used with DSLs, like here and here , but these DSLs have a predefined grammar in the preprocessor, that it uses to generate C++ code.此外,预处理器可以与 DSL 一起使用,例如herehere ,但这些 DSL 在预处理器中具有预定义的语法,用于生成 C++ 代码。 Its not really blurring the lines since it uses a different grammar.它并没有真正模糊线条,因为它使用了不同的语法。

Macros have one notable feature - they are very easy to abuse and rather hard to debug.宏有一个显着的特点——它们很容易被滥用,而且很难调试。 You can write just about anything with macros, then macros are expanded into one-liners and when nothing works you have very hard time debugging the resulting code.您可以使用宏编写几乎任何东西,然后将宏扩展为单行代码,当没有任何效果时,您很难调试生成的代码。

The feature alone makes one think ten times on whether and how to use macros for their task.仅此功能就让人思考十次是否以及如何使用宏来完成他们的任务。

And don't forget that macros are expanded before actual compilation, so they automatically ignore namespaces, scopes, type safety and a ton of other things.并且不要忘记宏在实际编译之前会被扩展,因此它们会自动忽略名称空间、范围、类型安全和大量其他内容。

The most important thing about macros is that they have no scope, and do not care about context.宏最重要的是没有scope,不关心上下文。 They are almost a dump text replacement tool.它们几乎是一个转储文本替换工具。 So when you #define max(.... then everywhere where you have a max it gets replaced; so if someone adds overly generic macro names in their headers, they tend to influence code that they were not intended to.因此,当您 #define max(.... 时,任何有最大值的地方都会被替换;因此,如果有人在其标题中添加了过于通用的宏名称,他们往往会影响他们不打算影响的代码。

Another thing is that when used without care, they lead to quite hard to read code, since no one can easily see what the macro could evaluate to, especially when multiple macros are nested.另一件事是,如果不小心使用它们,它们会导致代码非常难以阅读,因为没有人可以很容易地看到宏可以计算出的结果,尤其是在嵌套多个宏时。

A good guideline is to choose unique names, and when generating boilerplate code, #undef them as soon as possible to not pollute the namespace.一个好的指导方针是选择唯一的名称,并且在生成样板代码时,尽快#undef 它们以免污染命名空间。

Additionally, they do not offer type safety or overloading.此外,它们不提供类型安全或重载。

Sometimes macros are arguably a good tool to generate boilerplate code, like with the help of boost.pp you could create a macro that helps you creating enums like:有时宏可以说是生成样板代码的好工具,例如在 boost.pp 的帮助下,您可以创建一个宏来帮助您创建枚举,例如:

ENUM(xenum,(a,b,(c,7)));

which could expand to可以扩展到

enum xenum { a, b, c=7 };

std::string to_string( xenum x ) { .... }

Things like assert() that need to react on NDEBUG are also often easier to implement as macros像 assert() 这样需要对 NDEBUG 做出反应的东西通常也更容易实现为宏

There a many uses where a C developper uses Macros and an C++ developper uses templates. C 开发人员使用宏和 C++ 开发人员使用模板有很多用途。

There obviously corner cases where they're useful, but most of the time it's bad habits from the C world applied to C++ by people that believe there such a language called C/C++显然存在一些有用的极端情况,但大多数时候,C 世界的坏习惯适用于 C++ 的人们相信有这样一种称为 C/C++ 的语言

So it's easier to say "it's evil" than risking a developper misuses them.所以说“它是邪恶的”比冒着开发者滥用它们的风险更容易。

Forcing the programmer to use proper naming for the macros... and better tools to track replacement of macros would fix most my problems.强迫程序员对宏使用正确的命名......以及更好的工具来跟踪宏的替换将解决我的大部分问题。 I can't really say I've had major issues so far... It's something you burn yourself with and learn to take special care later on.到目前为止,我真的不能说我遇到了重大问题......这是你自己烧伤自己并学会以后特别小心的事情。 But they badly need better integration with IDEs, debuggers.但他们迫切需要更好地与 IDE、调试器集成。

  1. Macros do not offer type safety宏不提供类型安全
  2. Problems where parameters are executed twice eg #define MAX(a,b) ((a)>(b)? (a): (b)) and apply it for MAX(i++, y--)参数执行两次的问题,例如 #define MAX(a,b) ((a)>(b)? (a): (b)) 并将其应用于 MAX(i++, y--)
  3. Problems with debugging as their names do not occur in the symbol table.调试问题,因为它们的名称不会出现在符号表中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM