看似毫无意义的#define函数

时间:2014-05-16 22:07:23

标签: c++ windows c-preprocessor

我遇到了一些代码:

BOOL CBlahClass::SomeFunction(DWORD *pdw)
{
    RETURN_FALSE_IF_FILE_DOESNT_EXIST

    //the rest of the code makes sense...
    //...
}

我所看到的一切都很有道理,除了我对这条线有一点疑问         RETURN_FALSE_IF_FILE_DOESNT_EXIST

我搜索了这个字符串,然后找到#define:

#define RETURN_FALSE_IF_FILE_DOESNT_EXIST \
        if (FALSE==DoesFileExist()) return FALSE;

我的问题是......到底是什么?有没有什么好的理由让#define像这样?为什么不写:

BOOL CBlahClass::SomeFunction(DWORD *pdw)
{
    if ( FALSE == DoesFileExist() ) return FALSE

    //the rest of the code makes sense...
    //...
}

我能想到这样做的唯一原因是,位更容易,并且更难以写出" RETURN_FALSE_IF_FILE_DOESNT_EXIST&#34 ;然后写出" if(FALSE == DoesFileExist())返回FALSE"。

任何人都有其他理由这样做吗?这类事有名字吗?

3 个答案:

答案 0 :(得分:7)

嗯,使用宏的想法是在特定检查可能发生变化的情况下简化维护。当你有一个宏时,你需要改变的只是宏定义,而不是遍历整个程序并单独更改每个检查。此外,它还为您提供了通过更改宏定义来完全消除此检查的机会。

通常,当一个人拥有相当全面的重复代码时,通常会将该代码分离为一个函数。但是,当重复的代码片段必须执行某些非正统操作时,例如退出封闭函数的return,宏基本上是唯一的选择。 (有人可能会同意这样的技巧应保留给调试/维护/系统级代码,应该在应用程序级代码中避免使用。)

答案 1 :(得分:4)

该宏可能是guard clause,它声明了一个先决条件。前提条件是某个文件必须存在,因为如果它没有调用该函数是没有意义的。作者可能希望保护条款明显不同于#34;常规"检查并使其成为一个明显的宏观。它在这里是一个宏只是一种风格偏好。

当您要求条件为真时,您可以编写保护条款。在名为parseFile()的函数中,您可能确实希望调用者检查该文件是否存在。但是在一个名为openFile()的函数中,该文件尚不存在是完全合理的(因此你不会有一个警卫)。

您可能更习惯于看assert(...)。但是,如果您不希望程序在条件错误时停止,并且您希望在NDEBUG出现时检查条件,该怎么办?您可以实现像assert_but_return_false_on_fail(...)这样的宏,对吗?而这就是这个宏的意义所在。替代assert()

glib是一个非常受欢迎的defines its precondition assertions喜欢的库 这样:

#define g_return_if_fail()
#define g_return_val_if_fail()
#define g_return_if_reached
#define g_return_val_if_reached()
#define g_warn_if_fail()
#define g_warn_if_reached

与glib中相同的代码为g_return_val_if_fail(DoesFileExist(), FALSE);

您粘贴的代码可能会更好。一些问题是:

  • 这是非常具体的。为什么不在任何条件下使它成为通用的而不是 只是文件是否存在?

  • 将返回值硬编码到名称中。

  • 它没有进一步的诊断。 glib函数记录相当详细 失败时出错,包括堆栈跟踪,以便您可以看到一系列调用 这导致了失败。

这些问题使得宏看起来很愚蠢和毫无意义。否则它似乎毫无意义。

或者说,作者可能会被宏观迷恋,而且我读得太多了。

答案 2 :(得分:0)

没有理由这样做。程序员编写这样的宏的常用方法是因为他们的代码具有多返回语句(如果可能的话应该避免使用 - 但有时候避免使用它是不明智的)。例如:

// Bad code

err_t func (void)
{
  err_t err;
  ...

  if(err == error1)
  {
    cleanup();
    return err;
  }

  ...

  if(err == error2)
  {
    cleanup();
    return err;
  }

  cleanup();
  return err;
}

由于代码重复,要在整个函数中进行清理是不好的做法。

更清晰地解决问题的古老方法是“在错误转到”,你在标签的最后添加一个标签。并做清理并返回那里。它实际上是一个有点可接受的解决方案,但是goto是禁忌所以人们回避它。相反,他们想出了类似的东西:

// Slightly less bad code

#define RETURN_FROM_FUNCTION(err) { cleanup(); return (err); }

err_t func (void)
{
  err_t err;
  ...

  if(err == error1)
  {
    RETURN_FROM_FUNCTION(err);
  }

  ...

  if(err == error2)
  {
    RETURN_FROM_FUNCTION(err);
  }

  RETURN_FROM_FUNCTION(err);
}

这会删除代码重复,因此程序更安全,更易于维护。这可能是你问题的答案。

但是这个宏仍然不是一个好的解决方案,因为它使得代码更难以阅读,因为C程序员擅长阅读C,但不善于阅读“家庭酿造,秘密宏语言”。当然,宏的常见问题是:类型安全,难以调试/维护等。

但是,所有上述方法(包括on-error-goto)都源于无法在框外思考。最佳解决方案是:

// proper code

err_t func (void)
{
  err_t err;

  allocate(); // allocate whatever needs to be allocated

  err = func_internal();

  cleanup(); // one single place for clean-up
  return err;
}


static err_t func_internal (void) // local file scope, private function
{
  err_t err;
  ...

  allocate_if_needed(); // or maybe allocation needs to be here, based on results

  ...

  if(if(err == error1)
  {
    return err;
  }

  ...

  if(err == error2)
  {
    return err;
  }

  return err;
}