为什么#define不好?

时间:2011-09-26 22:51:28

标签: c++ c iostream c-preprocessor

  

可能重复:
  When are C++ macros beneficial?
  Why is #define bad and what is the proper substitute?

有人告诉我#define很糟糕。好吧,老实说我不明白为什么不好。如果它不好,那么我可以用其他方式做到这一点呢?

#include <iostream>
#define stop() cin.ignore(numeric_limits<streamsize>::max(), '\n');

5 个答案:

答案 0 :(得分:6)

#define本身并非错误。但是,通常有更好的方式来做你想做的事情。考虑inline函数:

inline void stop() {
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
}

(真的,你甚至不需要inline这样的功能。只需一个简单的普通功能就可以了。)

答案 1 :(得分:3)

这很糟糕,因为它不分青红皂白。您在代码中停止()的任何地方都将被替换。

解决问题的方法是将代码放入自己的方法中。

答案 2 :(得分:3)

它不一定是坏,只是人们过去使用它的大多数事情都可以用更好的方式完成。

例如,您提供的代码段(和其他代码宏)可能是内联函数,类似于(未经测试):

static inline void stop (void) {
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
}

此外,还有其他一些代码宏迫使你做“宏体操”的事情,比如如果你想打电话给写得很糟糕的话:

#define f(x) x * x * x + x

使用:

int y = f (a + 1);  // a + 1 * a + 1 * a + 1 + a + 1 (4a+2, not a^3+a)
int z = f (a++);    // a++ * a++ * a++ + a++

由于操作符的优先级,第一个将完全让您惊讶于结果,第二个将给您未定义的行为。内联函数不会遇到这些问题。

宏的另一个主要用途是提供枚举值,例如:

#define ERR_OK    0
#define ERR_ARG   1
: :
#define ERR_MEM  99

使用枚举更好地完成这些工作。

宏的主要问题是替换是在翻译阶段的早期完成的,因此信息经常丢失。例如,调试器通常不知道ERR_ARG,因为它会在创建调试信息的转换过程部分之前很久才被替换。

但是,对他们进行了足够的诽谤,他们仍然用于定义可用于条件编译的简单变量。这就是我现在在C ++中使用它们的全部内容。

答案 3 :(得分:3)

在C ++中,使用#define并不是强制性的,尽管应该首选替代方案。有一些上下文,例如include guards,其中没有其他便携式/标准替代方案。

应该避免这种情况,因为C preprocessor在编译器之前运行(顾名思义)。它执行简单的文本替换,而不考虑其他定义。这意味着输入到编译器的结果有时没有意义。考虑:

// in some header file.
#define FOO 5

// in some source file.
int main ()
{
    // pre-compiles to: "int 5 = 2;"
    // the compiler will vomit a weird compiler error.
    int FOO = 2;
}

这个例子似乎微不足道,但存在真实的例子。某些Windows SDK标头定义:

#define min(a,b) ((a<b)?(a):(b))

然后代码如下:

#include <Windows.h>
#include <algorithm>
int main ()
{
    // pre-compiles to: "int i = std::((1<2)?(1):(2));"
    // the compiler will vomit a weird compiler error.
    int i = std::min(1, 2); 
}

当有替代品时,请使用它们。在发布的示例中,您可以轻松地编写:

void stop() {
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
}

对于常量,使用真正的C ++常量:

// instead of
#define FOO 5

// prefer
static const int FOO = 5;

这将保证您的编译器看到与相同的事情,并且在嵌套作用域中使用名称覆盖使您受益(本地FOO变量将覆盖全局{{1}的含义如预期的那样。

答案 4 :(得分:2)

#define本身并不坏,但确实有一些不好的属性。我将列出一些我所知道的事情:

“功能”未按预期运行。

以下代码似乎合理:

#define getmax(a,b) (a > b ? a : b)

...但如果我这样称呼它会发生什么?:

int a = 5;
int b = 2;
int c = getmax(++a,b);    // c equals 7.

不,这不是拼写错误。 c将等于7.如果您不相信我,请尝试一下。仅此一点就足以吓唬你了。

预处理器具有固有的全局性

每当您使用#define定义一个函数(例如stop())时,它会在被发现后在所有包含的文件中起作用。

这意味着您实际上可以更改未编写的库。只要他们在头文件中使用函数stop(),您就可以更改未编写且未修改的代码的行为。

调试更加困难。

预处理器在代码进入编译器之前进行符号替换。因此,如果您有以下代码:

#define NUM_CUSTOMERS 10
#define PRICE_PER_CUSTOMER 1.10

...

double something = NUM_CUSTOMERS * PRICE_PER_CUSTOMER;

如果该行有错误,那么您将不会在错误消息中看到方便的变量名称,而是会看到如下内容:

double something = 10 * 1.10;

这使得在代码中查找内容变得更加困难。在这个例子中,它看起来并不那么糟糕,但如果你真的养成了这样做的习惯,那么你可能会遇到一些真正的麻烦。