宏是否使代码更具可读性?

时间:2012-07-04 16:51:09

标签: c++ c code-readability

我对宏及其可读性进行了辩论。 我认为在某些情况下使用宏可以使代码更短,更易于理解,并且阅读时不那么累。

例如:

#include <iostream>

#define EXIT_ON_FAILURE(s) if(s != 0) {std::cout << "Exited on line " << __LINE__ << std::endl; exit(1);}

inline void exitOnFailure(int s, int lineNum) {
    if (s != 0) {
        std::cout << "Exited on line " << lineNum << std::endl; 
        exit(1);
    }
}

int foo() {
    return 1;
}

int bar(int a, int b, int c) {
    return 0;
}

int main() {
    // first option
    if (foo() != 0) {
        std::cout << "Exited on line " << __LINE__ << std::endl;
        exit(1);    
    }
    if (bar(1, 2, 3) != 0) {
        std::cout << "Exited on line " << __LINE__ << std::endl;
        exit(1);    
    }

    // second option
    EXIT_ON_FAILURE(foo());
    EXIT_ON_FAILURE(bar(1, 2, 3));

    // third option
    exitOnFailure(foo(), __LINE__);
    exitOnFailure(bar(1, 2, 3), __LINE__);

    return 0;
}

我更喜欢这里的第二个选项,因为它简短而紧凑,大写锁定文本比驼峰的情况更清晰,更容易阅读。

这种方法有什么问题,特别是在C ++中,还是只是糟糕(但可以接受)的风格?

3 个答案:

答案 0 :(得分:3)

宏是C / C ++的一个非常强大的功能,就像所有C功能一样 默认情况下指向你的脚。考虑以下用途 你的宏:

if (doSomething())
    EXIT_ON_FAILURE(s)   /* <-- MISSING SEMICOLON! OH NOES!!! */
else
    doSomethingElse();

else是否属于声明中的ifif 通过展开EXIT_ON_FAILURE创建?无论哪种方式,行为 一个缺少分号是完全出乎意料的。如果是EXIT_ON_FAILURE() 是一个函数,你会得到一个编译器错误。在这种情况下,你会得到 编译但做错事的代码。

这就是宏的问题。它们看起来像功能或 变量但不是。一个写得很糟糕的宏是礼物 继续给予。每次使用宏都是一个潜在的微妙的bug 对宏的每次更改都有可能将逻辑错误引入代码中 那你没碰过。

一般情况下,除非绝对必要,否则应避免使用宏。

如果您需要定义常量,请使用const变量或enum。 一个好的编译器(你可以免费获得)将把它们变成 生成的可执行文件中的文字就像#define'd常量一样 但它也会按照您的期望和意愿处理类型转换 显示在调试器的符号表中。

如果您需要类似内联函数的内容,请使用内联函数。 C ++和C99都提供了它们和大多数体面的编译器(包括 免费的)已经做了很长时间的延期。

函数强制它们的参数被评估,与宏不同,所以

inline int DOUBLE(int a) {return a+a;}

只会评估a一次

#define DOUBLE(a) (a + a)

将评估a两次。这意味着

x = DOUBLE(someVeryLongFunction());
如果DOUBLE是一个宏,那么

将花费两倍的时间,而不是它是一个函数。

另外,我(故意)忘了将宏参数括起来,所以 这样:

DOUBLE(a << b)

将给出一个完全令人惊讶的结果。你需要记住 将DOUBLE宏写为

#define DOUBLE(a) ((a) + (a))

换句话说,您需要完美地编写一个宏来最小化 在脚下射击自己的机会。如果你弄错了, 你会支付多年的费用。

所有这一切,是的,宏将会出现的情况 代码更具可读性。它们很少而且很远,但它们 存在。其中之一是您的代码重新发明的assert宏。 复杂系统使用自己的自定义很常见 assert - 像宏一样绑定到本地调试方案,以及那些 几乎总是用宏来实现,以获得__FILE____LINE__和条件的文本。

但即便如此,这就是典型assert的实施方式:

#ifdef NDEBUG
#   define assert(cond)
#else
#   define assert(cond) __assert(cond, __FILE__, __LINE__, #cond)
#endif

换句话说,类似函数的宏扩展为函数调用。 这样,当您致电assert时,扩展仍然非常接近 它看起来像什么,论证扩展发生的方式 你期待它。

还有一些其他用途。基本上,任何时候你需要 将信息从构建过程本身传递给程序,它会 可能需要通过宏系统。即便如此,你呢 应该最小化触及宏的代码量以及如何 宏做的很多。

最后一件事。如果您想要使用宏,那么 代码会更快,请注意这是魔鬼说话。在 在过去的日子里,可能会出现转换小的情况 功能到宏中带来了显着的性能提升。这些 天虽然:

  1. 大多数编译器都支持内联函数。有些人甚至这样做 自动转为静态功能。

  2. 现代计算机速度如此之快,以至于你几乎肯定不会注意到 甚至调用一个微不足道的函数的开销。

  3. 只有当您的编译器不执行内联函数时,您才能做到这一点 用更好的代替你 证明该函数调用 开销是一个瓶颈,你可以证明编写一些宏是合理的。

    可能。

答案 1 :(得分:0)

确保宏可以简化功能,使其更易于阅读。但是你应该考虑使用内联函数。

在您的示例中,EXIT_ON_FAILURE可以是内联函数。宏不仅使编译器错误不准确(它可能导致一些错误显示在错误的地方),使用宏时要注意一些事情,特别是变量,请考虑这个例子:

#define MY_MACRO(s) if (s * 2 >= 20) foo()

// later on your code:
MY_MACRO(5 + 5);

虽然我可以期待foo()给我打电话,但它不会,因为它不会扩展到if (10 * 2 >= 20) foo(),它会扩展为if (5 + 5 * 2 >= 20) foo()。所以你需要记住在定义宏时总是在变量周围使用()。

宏也使程序更难调试。

答案 2 :(得分:0)

有时候你需要的是宏,但你应该尽量减少它们的数量。在您的示例中,已经存在一个名为“assert”的宏,您可以使用该宏而不是创建新宏。

C ++有许多功能允许你在没有需要C语言宏的宏的情况下做事,所以宏在C ++代码中应该比在C代码中更少见。