过度依赖宏

我觉得,每次我读C或C ++程序时,其中一半或更多只是宏。 我知道宏可以很酷,但它们很难跟踪,调试等。更不用说大多数编程语言甚至没有定义像宏这样的东西(尽管Perl6会有类似的东西)。

我个人总是找到一种方法来编写我的代码而不使用宏,无论是模板,多重inheritance等。我甚至觉得我不是一个优秀的程序员,因为所有的专业人员使用宏,我尽量避免使用它们如我所能。

问题是,如果没有宏,是否存在无法解决的问题? 宏最终是一个好/坏的做法? 我什么时候应该考虑使用宏?

是的,这是一个。 当您需要以一种配置包含它而另一种完全省略的方式将跟踪代码添加到您的程序时,您必须使用宏。

就像是:

#ifdef WITH_LOGGING #define LOG( x ) DoLog( x ) #else #define LOG( x ) #endif 

现在你用这种方式:

 LOG( L"Calling blahblahblah with " + getSomeStringHardToCompute() ); 

并且在使用WITH_LOGGING的配置中,您拥有该代码,否则它将被完全省略 – 甚至不存在于二进制文件中,因此

  • 它无法帮助其他人分析您的程序
  • 你得到一个较小的二进制
  • 该计划根本不浪费时间
  • 编译器可以生成更好的优化代码。

直接来自Scott Myer的Effective C ++ – > 1

考虑到可用性和内联的可用性,您对预处理器的需求会减少,但并未完全消除。 当你放弃#include时,这一天远非如此,#ifdef / #ifndef继续在控制编译方面发挥重要作用。 目前还没有时间退出预处理器,但你绝对应该计划开始更长时间和更频繁的假期。

你一直在看一些糟糕的C ++代码。 我使用宏的地方仅限于:

  • 头卫
  • 非常偶然的条件编译
  • 投掷宏的一般例外
  • 一般调试/日志输出宏

我不认为这四个是可以避免的。

可以使用常量标志或调试function来控制调试行为。 所以这是我不可避免的列表:

  • 多重包含保护。
  • 宏是符号字符串化的唯一方法。 断言宏,紧凑的const string&stringify实现(枚举类别值);

例:

 const char* stringify(enum category value) { #define c(x) case x: return #x; switch(value) { c(CIRCLE) c(RECTANGLE) c(TRIANGLE) default: return "UNKNOWN"; } #undef c // the most important part } 

当然,当您想要在预处理期间生成代码时,宏也很有用。 虽然可以使用模板来避免这种情况(请参阅此问题和讨论 – C ++模板是伪装的宏吗? ),如果它可以让用户的生活更轻松,您可以使用宏 – 请参阅“googletest”项目( https:/ /github.com/google/googletest/ )有效地使用宏。 您显然不希望使用宏来生成需要调试的代码,而是使用模板。

我认为C ++的模板和内联函数使得宏几乎可以避免。

宏的普遍存在可能是因为有许多C ++程序员曾经是C程序员。 这些人可能会精通使用宏(因为它有时真的是纯C中最好的或唯一的解决方案),如果他们已经知道如何解决问题,可能看不到学习更复杂的C ++特性的任何意义。 至少在开源世界中,有许多C转换器,所以你自然会遇到C范例。 我不认为你是一个糟糕的程序员,如果你避免这样的function,许多人都这样做,就像GOTO一样。

C(以及C ++)是一种非常灵活的编程语言。 这很好,因为每个人都可以发展自己独特的风格,并以几种不同的方式解决大多数问题。 然而,这也可以被认为是一个问题。 在我看来,这不是一个应该由语言解决的问题,而是通过建立惯例来解决的。

C ++中有许多function可以安全地忽略。 也许有一些奇怪的特殊场合,这样的function真的是最好的方法,但在大多数情况下,你可以没有:

  • 朋友class
  • GOTO等等。

IMO,一个高级C ++程序员应该能够至少能够流利地阅读它们 – 但我希望一个好的程序员在何时以及是否使用臭名昭着的function时要仔细考虑。

如果没有宏,我有很多问题无法解决。 例如,某些结构的序列化/反序列化

 #define STRUCT_DESCRIPTION structname(MyStruct)member(int,a)member(double,b)member(long,c)
 #include“declare_serializable_struct.h”//声明struct本身并生成序列化/反序列化代码
 #undef STRUCT_DESCRIPTION

(也可以使用BOOST_PP_SEQUENCE)另一个例子 – 使用消息映射调度消息,即生成如下的开关:

开关(message_type)
 {
 case msg1:on_msg1(msg); 打破;
 case msg2:on_msg2(msg); 打破;
 ...
 }

并使用一些消息描述表(“map”)同时生成处理程序方法声明on_msgX(msg)

就个人而言,我尽可能避免使用宏,但我没有成功。

但是,c ++ 0x中的lambdas允许将任意代码内联到“用户或库定义的语言语句”这样的foreach循环中,因此宏领域失去了重要的部分:)

宏是条件编译的解决方案(通过ifdefifndef )。 以下是示例:

1)

 #ifndef MY_HEADER_HPP #define MY_HEADER_HPP //... #endif 

2)

 #ifdef __cplusplus #define BEGIN extern "C" { #define END } #define NULL (0); #else #define BEGIN #define END #define NULL ((void*)0); #endif //------------------------- BEGIN void my_function(char* str); END //------------------------- void my_function(char* str) { if(str != NULL) { //... } } 

内联函数模板取代了C ++中宏的其他用法。

我倾向于尽可能避免使用宏,因为它们存在明显的安全/调试问题,但是有时候宏提供了语言中没有其他function可以优雅地做的事情,在这种情况下我更喜欢使用宏只是因为它让我的生活(和我的同事们的生活)变得更容易。

例如,我创建了一个Enum类,它在struct (范围)中包含枚举并添加了一些function:

  • 迭代的可能性(暗示值的顺序)
  • 转换为/从字符串转换(方便读取/写入文件,写入日志)

为了创建枚举,我使用一个宏来自动生成转换器(往返)和迭代向量。

当然我可以没有一个,毕竟宏仅用于代码生成。 但是没有人就意味着违反DRY,并且在我自己的偏好中“干”>“不要使用宏”。 因为一旦调试宏是安全的,而DRY违规是维护的噩梦。

现在,一旦我发现如何不违反DRY,我就全力抛弃这个宏。 想法显然是受欢迎的…外部脚本不是更好;)

我的2美分。

我也试图避免使用宏,但为了扩展调试,我还没有找到一种方法来在调试时打印文件名,函数名和行号。

我通常有一个名为DebugLog.h的头文件,其中包含以下宏

 #define DEBUG(debugMessage) \ printf("%s | %s [%d] - %s\n", __FILE__, __PRETTY_FUNCTION___, debugMessage); 

使用:DEBUG(“测试”)将输出如下内容:

 main.cpp | foo(void)[20] - Test 

您可以调整C ++和其他调试语句的宏。 也可以修改宏以将结果字符串发送到记录器。

我开始在一家电信公司工作。 产品代码库大约有20年的历史,必须支持许多传统产品,同时还要避免重复代码。 使用的语言是C ++ 03。 我发现很多类似于以下的结构

 ClassA::methodA(...) { // Common code ... #if defined(PRODUCT_A) || defined(PRODUCT_B) // Code for Product A or Product B ... #elif defined(PRODUCT_C) // Code for product C ... #endif // Common code ... } 

可怕的东西,我同意。 到目前为止,我们还没有找到更好的解决方案。 至少通过这种方法,我们可以通过简单的代码读取来理解代码应该做什么。

问题是,如果没有宏,是否存在无法解决的问题?

没有。

宏最终是一个好/后练习? 我什么时候应该考虑使用宏?

在不支持或不支持inline关键字的语言中,宏是重用代码的好方法,但同时避免了任何代码中的函数调用的开销,这些代码的紧密循环足以使其变得庞大区别。

你对用宏填充代码的咆哮可能是合理的。 确实很难调试,在某些情况下还要阅读。 但它们确实在极少数情况下有用,在这种情况下,像这样的优化是真正的保证。

请注意,从C99开始,C现在可以使用inline关键字执行显式内联函数,这样可以减少对宏的需求,甚至比使用宏更有优势 。

编程语言宏适用于所有宏都有用的东西:避免一遍又一遍地输入相同的东西。 因此,如果您发现自己在许多地方编写相同的代码片段,为什么不用它来制作宏? 特别是如果您正在编写库,使用宏可以使尝试使用该库的人的生活更轻松。 看看几乎所有的GUI工具包(Qt就是一个例子)。 它们都广泛使用宏。