【问题标题】:Is it bad practice to have a macro definition return a value for a function?让宏定义返回函数的值是不好的做法吗?
【发布时间】:2011-08-09 23:39:10
【问题描述】:

使用定义的宏来有条件地返回一个值有一个缺点,即仅查看客户端代码并不明显,可能会在宏点退出。

我正在考虑的用例是编写值和错误检查,如下所示:

#define WRITE_CHK(file, param)\
if (!write_that_returns_zero_on_fail(file, param)) {\
   handle_error();\
   return false;\
}

客户端代码:

bool myfunc()
{
   ...
   WRITE_CHK(file, param) // function might return here
   ...
   return true;
}

我很好奇宏(将在我的代码中的许多地方使用)的好处是否会超过上面提到的缺点。 除了简单扩展(不使用宏)之外,还有其他首选方法吗?

【问题讨论】:

  • 嗯,通常的 C++ 方法是让“write”函数本身在出错时抛出异常,而不是不断检查返回码。有同样的“可能令人惊讶的提前返回”问题,但任何使用异常的程序也是如此......
  • 我想要一个与 c 兼容的解决方案
  • 请告诉我为什么我的问题被否决,以便我可以改进未来的问题
  • 人们可能对您的问题投了反对票,因为它似乎是主观的。以“这是不好的做法”开头的问题通常在这里得到不好的评价。事实上,有人将其标记为“不具建设性”或离题。我碰巧不同意这一点,因为我认为你在这里和那里提出了一个有用的问题很好的答案(如 Oli 的),这不仅仅是个人意见或可能引发争论。我赞成将其保持打开状态,但您可能需要稍微改一下标题。
  • 宏的另一个问题是调用端的语法异常。它不能代替正确的语句,并且您有悬空的else 问题。您可以附加 else (void)0 以摆脱两者。

标签: c++ c macros


【解决方案1】:

标准答案是“不要使用宏”;但这通常有点简单化。在某些情况下,它们可以大大减少您原本应该拥有的样板冗长。

那么,为什么不将事实编码到宏名称中呢?例如WRITE_OR_RETURN_ON_FAILURE。它可能有点冗长,但不太可能绊倒您的代码读者。

【讨论】:

  • 我想在这里给你+0.5。您的建议是完全有效的,但宏观回报方法有异味,更好的答案将涉及一些替代方法。无论如何+1。 :)
  • @Jonathan:是的,我考虑过。但有时你真的只是想把这个乏味的模式包起来,而一个辅助函数就是做不到的。
  • 很公平。尽管如此,这条路还是有龙。
  • 是的,感谢您理解用法和有效建议。命名可以做很多事情!
  • 同意——只要宏名称中包含动词RETURN,就足够了。
【解决方案2】:

在宏中隐藏控制流并不常用,因此可能会让必须理解或调试代码的开发人员感到困惑。

我建议不要完全使用宏,但如果必须使用它们,请确保控制流部分是明确的。

// assume handle_error returns true
#define WRITE_FAILED(file, param)\
(!write_that_returns_zero_on_fail(file, param) && handle_error())

// macro usage
if(WRITE_FAILED(file, param))
{
    return;
}

要问自己的另一个问题是——为什么还要在这里使用宏?如果要隐藏控制流,那不是一个好主意。还有其他原因吗?

【讨论】:

    【解决方案3】:

    制作一个两步宏:

    #define WRITE_CHK_(file, param, HANDLER)\
    if (!write_that_returns_zero_on_fail(file, param)) {\
        handle_error();\
        HANDLER\
    }
    
    #define RFALSE return false;
    
    #define WRITE_CHK(file, param) WRITE_CHK_(file, param, RFALSE)
    

    如果您需要在其他地方进行类似检查,则可以制作其他包装器(或直接使用WRITE_CHK(file, param, return false)

    【讨论】:

    • 如何解决这个问题?
    • @Oli 如果你想让它表明你返回 false,你可以使用类似 WRITE_CHK(file, param, return false) 的东西。
    • 是的,确实如此。我认为如果建议采用这种方法,您的答案会更好!
    • @Oli 已编辑。我多次使用这种宏模式是它的第二天性
    • 答案本身很复杂,但通过 Oli 的 cmets 和您的回答,我想我明白您在说什么。很有趣,谢谢。
    【解决方案4】:

    是的,这很糟糕。使用goto

     #define fail_unless(x) do {if(!(x)) goto fail;} while (0)
    
     int foo()
     {
         fail_unless( my_write(...) );
         fail_unless( bar() );
         return 0;
     fail:
         cleanup();
         return -1;
     }
    

    编辑:如果您因为不了解 while 循环在那里做什么而投反对票,那么您应该问一下。这是确保 C 宏在任何上下文中都充当单个语句的标准技术。如果您的宏扩展为 if 语句(就像其他被赞成的答案中的宏),那么您会得到意想不到的副作用,例如:

     #define DONT_DO_THIS(x) if (!x) { foo; }
    
     if (a) DONT_DO_THIS(x)
     else something_else();
    

    你希望something_else 在!a 时被执行,但宏实际上扩展为:

     if (a)
         if (!x) { foo;}
         else something_else();
    

    something_else 在a && x 时执行,而不是在!a 时按照程序员的意图执行。

    【讨论】:

    • 你是认真的吗?这如何更好地消除惊喜?
    • 我当然是认真的。函数中的两个退出点都是可见的,fail 标签告诉你发生了什么。此外,没有使用像 fsmc 的 WRITE_CHK 这样的宏,它会写入和检查并可能返回(然后是类似的 BAR_CHK 用于 bar 等),而是使用单个 CHECK 宏。我称它为效仿他的示例,但我会使用 fail_check 之类的东西,加强宏和标签之间的联系。
    • @Foo Bah 它概括得比你自己的答案好得多。
    • 我不认为您对使用 while 循环的标准模式的解释是为什么 任何人 否决了这个...
    • 显然不可能,因为解释是在否决票之后出现的。你的意思是什么?
    猜你喜欢
    • 2014-08-15
    • 2010-11-23
    • 2015-10-29
    • 2021-05-07
    • 1970-01-01
    相关资源
    最近更新 更多