过度依赖宏

时间:2010-08-13 07:57:23

标签: c++ c macros

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

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

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

13 个答案:

答案 0 :(得分:18)

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

类似的东西:

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

现在你以这种方式使用它:

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

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

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

答案 1 :(得分:10)

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

  

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

答案 2 :(得分:9)

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

  • 标题警卫
  • 非常偶然的条件编译
  • 抛出宏的一般例外
  • 一般调试/日志输出宏

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

答案 3 :(得分:4)

可以使用常量标志或调试功能来控制调试行为。所以这是我不可避免的清单:

  • 多重包含保护。
  • 宏是符号字符串化的唯一方法。断言宏,紧凑的const string& amp; 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
}

答案 4 :(得分:2)

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

答案 5 :(得分:1)

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

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

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

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

  • 朋友班
  • GOTO语句 还有更多。

IMO,一位资深的C ++程序员应该至少能够流利地阅读它们 - 但我希望一个优秀的程序员在何时以及是否使用臭名昭着的功能时要仔细考虑。

答案 6 :(得分:1)

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

#define STRUCT_DESCRIPTION structname(MyStruct) member(int,a) member(double,b) member(long, c)
#include "declare_serializable_struct.h" // declares struct itself and generates serialization/deserializaton code
#undef STRUCT_DESCRIPTION

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

switch(message_type)
{
case msg1: on_msg1(msg); break;
case msg2: on_msg2(msg); break;
...
}

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

就个人而言,我尽可能避免使用宏,但是我没有以这种方式成功。

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

答案 7 :(得分:1)

宏是条件编译的解决方案(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 ++中其他宏的使用。

答案 8 :(得分:1)

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

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

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

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

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

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

我的2美分。

答案 9 :(得分:1)

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

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

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

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

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

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

答案 10 :(得分:1)

我已经开始在一家电信公司工作。产品代码库已有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
  ...
}

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

答案 11 :(得分:-1)

  

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

没有

  

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

在不支持或尊重inline关键字的语言中,宏是重用代码的好方法,但同时避免在任何紧密循环的代码中调用函数的开销足以让它产生巨大的变化。

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

请注意,从C99开始,C现在可以使用inline关键字执行显式内联函数,从而减少对宏的需求,甚至has advantages over using macros

答案 12 :(得分:-3)

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