2010-08-13 57 views
15

我觉得,每次我读一个C或C++程序时,它的一半或更多只是宏。我明白,宏可能很酷,但它们很难跟踪,调试等。更不用说大多数编程语言甚至没有定义像宏一样的东西(虽然Perl6会有类似的东西)。过度依赖宏

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

问题是,有没有宏无法解决的问题?宏是否最终是好的/坏的做法?我应该何时考虑使用宏?

+0

那么,你不能在C中使用模板,所以这是一个原因。 – detly 2010-08-13 09:07:38

+7

如果您觉得“所有专业人员都使用宏”,您一直在阅读什么代码?我知道的所有理智的C++代码都很难避免它们。在C中,它有点不同,因为你没有多种选择。顺便说一句,为什么这个标签C呢?你的问题*听起来*非常特定于C++,提及模板和继承。你知道,C和C++是不同的语言。 – jalf 2010-08-13 10:23:30

+0

@jalf说的 - 他们可能有一些相似之处,但是当涉及代码生成时,他们完全不同的行星。例如,[X-macro/table generation idiom](http://stackoverflow.com/questions/2927245/looking-for-a-good-explanation-of-the-table-generation-macro-idiom) – detly 2010-08-13 10:37:29

回答

16

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

喜欢的东西:

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

现在你用这种方式:

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

,并与WITH_LOGGING配置你的代码否则它完全省略 - 即使不存在在二进制中,因此

  • 它并不能帮助别人分析程序
  • 你会得到一个更小的二进制
  • 程序不会浪费时间在FO所有
  • 记录编译器可以产生更好的优化代码。
+0

还经常用于添加在调试模式下运行但不在发布模式代码中运行的代码。 – 2010-08-13 08:03:07

+0

这是一个例子,一个宏有用,但不是没有宏的情况下根本无法完成的一个例子。例如,如果你明白我的意思,你可以在DoLog()的每个调用周围放一个#ifdef块。 – thomasrutter 2010-08-13 08:03:42

+1

这是模板或内联函数。不需要宏。 – Basilevs 2010-08-13 08:03:54

-1

问题是,有没有宏不能解决的问题?

是宏最终是好/后退的做法?我应该何时考虑使用宏?

在不支持或兑现inline关键字语言,宏重复使用代码的好方法,但同时避免任何代码的函数调用是紧密环绕足够的开销为此做出巨大的改变。

您对代码中散布着宏的咆哮可能是有道理的。确实很难调试,并且在某些情况下需要阅读。但是,在非常少量的情况下,这些优化确实是有保证的。

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

+2

挑剔的是,你需要包含宏和条件编译的宏,并且在许多框架中,如果禁用了日志记录,就可以避免记录成本。 – 2010-08-13 08:07:47

+1

同意David。有些问题只能用宏来精确地解决(优雅地说是一个相对术语)。 – 2010-08-13 08:17:14

+0

不,因为有和没有宏的C++是图灵完整的? :) 但是有很多情况下,只有宏(不是模板或内联)允许避免代码重复。我同意,尽可能避免宏应该是一个意图,但诚实的答案必须是“是”。 – user396672 2010-08-13 09:54:40

8

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

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

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

+3

特别是因为记录/投掷你一般希望自动提取文件名和行号。 – 2010-08-13 14:01:38

-4

编程语言宏适用于所有宏都适用的内容:避免一遍又一遍地输入相同的内容。所以,如果你发现自己在很多地方都写了相同的代码,为什么不从它制作一个宏呢?尤其是如果你正在编写一个库,使用宏可以让试图使用这个库的人更容易。看看几乎所有的GUI工具包(Qt就是一个例子)。它们都广泛使用宏。

+2

这就是我们使用函数的原因,而不是使用宏的原因。 – 2010-08-13 08:04:48

+0

这不是宏的一个很好的理由有更安全的解决相同的问题(功能)更优雅的解决方案 – 2010-08-13 08:18:46

+0

当一个函数不能做到这一点,当你应该达到预处理器宏 – TerryP 2010-08-14 17:18:00

9

斯科特Myer百货公司有效的C++直 - > 1

鉴于consts和内联的可用性,您的预处理器需求减少,但它并没有完全消除。当你可以放弃#include时,这一天远远不够,#ifdef /#ifndef在控制编译中继续扮演重要角色。现在还没有时间退休预处理器,但你应该明确地计划开始给它更长,更频繁的休假。

1

当你想在预处理过程中生成代码时,宏也是很有用的。尽管可以使用模板避免这种情况(请参阅此问题和讨论 - Are C++ Templates just Macros in disguise?),但如果它可以使用户的生活变得更加轻松,那么可以使用宏 - 请参阅'googletest'项目(https://github.com/google/googletest/)如何有效地使用宏。您显然不想使用宏来生成需要调试的代码,而是使用模板。

4

调试行为可以通过常量标志或调试功能来控制。所以这里是我不可避免的列表:

  • 多重包容性保护。
  • 宏是符号串化的唯一方法。 assert宏,紧凑的实现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 
} 
0

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

宏的普遍存在可能是由于许多C++程序员曾经是C程序员。这样的人可能会熟练使用宏(因为它有时真的是纯C中最好或唯一的解决方案),并且如果他们已经知道如何解决问题,则可能没有意识到学习更复杂的C++功能。至少在开源世界中,有许多C转换器,所以你自然会遇到C范例。如果你避免了这种功能,我认为你不是一个糟糕的程序员,很多人就像GOTO一样。

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

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

  • 朋友类
  • GOTO语句 多。

国际海事组织,高级C程序员应该至少能够流利地阅读它们 - 但我希望一个好的程序员仔细考虑何时以及如果使用臭名昭着的功能。

0

有许多问题,我无法解决没有宏。 例如,串行化/一些结构的反序列化

 
#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; 
... 
} 

和生成处理程序的方法声明on_msgX(msg)在同一时间使用一些消息描述表(“地图”)

就个人而言,我尽量避免宏,但我没有成功我这样。

然而,lambda表达式中的C++ 0x允许内嵌任意代码为“用户或库定义languge报表”这样的foreach循环,使宏观领域失去了显著部分:)

0

也许这是一个有点偏离主题,但我正在阅读post关于如何减少编译时间。作者观察到,在一个单独的文件中包含每行代码和一个宏(是的,这很丑),通过强大的比例(两个参数约为70%)减少了编译时间和大小。

任何解释?

+0

作者帖子有一些解释,但我主要将它归结为:(1)错误的编码实践 - 为什么每个源文件似乎都包含每个标题,而不仅仅是与其相关的标题?和(2)Windows上的文件系统访问速度非常慢。 – 2010-08-13 09:38:36

+0

是的,我的问题并不准确,但特别令我惊讶的是二进制文件大小的减少。 为什么包含太多的头部比需要的是影响文件的大小? – Nielk 2010-08-13 09:48:27

0

宏是有条件编译的解决方案(由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++中。

0

我倾向于尽可能避免使用宏,因为它们有明显的安全/调试问题,但有时候宏会提供语言中没有其他功能优雅的东西,在这种情况下,我更喜欢使用宏只是因为它使我的生活(以及我的开发人员)更加轻松。

例如,我已经创建了一个Enum类,它包装枚举在struct(范围),并增加了一些功能:迭代

  • 可能性(这意味着该值的顺序)
  • 转换为字符串(方便读取/写入文件,写入日志)

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

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

现在,我尽快发现如何不违反DRY就会抛弃这个宏。想法显然是受欢迎的...和外部脚本是不是更好;)

我2美分。

0

我尽量避免使用宏,但为了扩大调试范围,在调试时我还没有找到打印文件名,函数名和行号的方法。

我通常有一个名为DebugLog.h一个头文件下面的宏

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

使用: DEBUG( “测试”) 将输出类似:

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

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