做{...} while(false)

Mr.*_*Boy 117 c++

我正在看一些个人的代码,并注意到他的功能似乎有一个模式:

<return-type> function(<params>)
{
 <initialization>

 do
 {
   <main code for function>
 }
 while(false);

 <tidy-up & return>
}
Run Code Online (Sandbox Code Playgroud)

不错,更奇特(实际代码相当简洁且不足为奇).这不是我以前见过的东西,我想知道是否有人能想到它背后的任何逻辑 - 也许是用不同语言的背景?

Tho*_*ing 169

你可以break出局do{...}while(false).

  • +1,因为这可能是代码的意图,但做这样的事情只是一个愚蠢的伪装goto.如果您认为goto是适合该工作的工具,那么您应该只使用#$(*#@ goto. (91认同)
  • 这不仅仅是一个伪装的goto.这是一个受限制的(结构化的)转到. (54认同)
  • @Anon.:前锋跳跃是对goto的限制,跳出绝对是一种限制.gotos的真正问题是意大利面条代码,以及前进和外跳限制. (43认同)
  • 实际循环在语义上不是goto.条件在语义上不是goto."转到函数的末尾并执行清理代码"**在语义上是一个goto.当语义适用时使用gotos,不要因为你害怕它们而在语义上装扮不同. (32认同)
  • 它以什么方式"受限制"?只有向前跳跃才不是"限制".一个goto是一个goto,并且让它看起来不像是一个比仅仅使用goto更糟糕. (19认同)
  • 我不同意goto倡导者:仅仅在函数中使用令牌"goto"会立即增加关于该函数的推理的认知负担.这不是关于<em>实际</ em>代码的作用,而是<em>可能</ em>做什么,基于你对它的不完全理解.我们的时间有限; 我们无法立即理解所有的一切.在那里有一个goto,不要让我紧张.如果你发现这个成语"怪异"或者不熟悉,你需要阅读更多代码,直到你熟悉它为止. (18认同)
  • 你不仅可以"突破",还可以"继续".呸. (15认同)
  • 就个人而言,我已经看到了这个成语足够多的时间,我不觉得它很尴尬.我之所以使用它而不是goto,我将不必搜索goto指向的标签(理论上可以在goto语句之前).相反,我所要做的就是遵循代码的缩进. (9认同)
  • 或者"继续".但我最好使用嵌套的`if`代替. (3认同)
  • 你不会通过这个技巧简化这段代码.所以应该避免.您可以使用简单的`if/else`逻辑替换它. (3认同)
  • 更好的技术是将代码块包装到函数中并返回函数.顺便说一句,我绝对更喜欢goto而不是`if(x){do {/*stuff*/} while(false)}`.并且不要让我使用像这样的巨大if语句而不给它一个块.Goto赢了. (3认同)
  • 你应该感恩的是,没有人想过把goto隐藏在一个说BREAK之类的宏后面. (2认同)
  • 我认为必须避免使用这个成语.你将无法在内循环中使用`break`来摆脱`do {...} while(false)`.嗯......一个多态的"休息"! (2认同)
  • 用 if/else 逻辑替换它有时可能会导致过度嵌套的 if(想象一下有 10 层括号!)。如果使用“do while false”有意义,那么避免丑陋代码的唯一合理替代方案就是使其成为一个函数并使用 return 而不是 break。这清楚地表明,它根本不是伪装的 goto,否则从函数提前返回也将是伪装的 goto。另外,我很惊讶有这么多人提倡使用 goto 来代替——永远不要使用 goto! (2认同)
  • 我过去广泛使用过这个。如果您正在执行一系列相关操作(FindRsrc、LoadRsrc、LockRsrc、GetRsrcPtr),则嵌套级别开始变得烦人,并且错误处理和清理可能会变得非常复杂。使用break,您可以记录失败,然后跳过相关代码并清理循环下方。你知道什么不需要清理,因为变量是 NULL。 (2认同)

Ben*_*igt 119

很多人都指出,它经常与休息一起用作编写"goto"的尴尬方式.如果它直接写在函数中,那可能就是这样.

在宏中,OTOH do { something; } while (false)是在宏调用之后强制分号的一种便捷方式,绝对不允许其他令牌跟随.

另一种可能性是,曾经有一个循环或预计将来会添加迭代(例如,在测试驱动开发中,不需要迭代来通过测试,但从逻辑上讲,如果在那里循环则有意义该功能需要比目前要求的更为通用)

  • +1用于在宏中提及此实用程序; 我很惊讶没有人提到过这个! (18认同)
  • 这不是很尴尬,它很有用,因为它是一个*Scoped*goto - 这意味着你在do循环中声明的任何变量都会被破坏,而goto不会这样做. (12认同)
  • @Paul:没有什么能阻止你在一堆语句中添加大括号来强制使用goto这种行为. (9认同)
  • 是的,宏观事实上是一个完全有效的用法.当然,外面的宏,它只是愚蠢......;) (7认同)
  • @Paul:转出一个块肯定会导致局部变量在C++中被销毁,就像break一样.并且在C中变量并没有真正被破坏,它们的堆栈空间被简单地回收. (5认同)
  • 同意,但我不明白第3段是如何获益的:要么是不再有意义并且应该被删除,或者你不应该首先拥有它 - 即使预期迭代,你应该添加它*然后*,在需要它之前是YAGNI. (2认同)

Hog*_*gan 24

作为goto的休息可能是答案,但我会提出另一个想法.

也许他想要一个本地定义的变量并使用这个结构来获得一个新的范围.

请记住,虽然最近的C++适用于{...}任何地方,但情况并非总是如此.

  • 在这种情况下,他本可以使用花括号. (29认同)
  • @Polaris @Nemanja,直到我用C/C++编程4年才发现你可以在任何地方创建一个新的本地范围`{}`*..这在`switch-case中特别方便`代码 (7认同)
  • @Nemanja,你会惊讶于有多少开发者不知道这一点,并尝试与Hogan建议的相关的东西 (2认同)
  • 它到底什么时候不允许括号?我开始编程就像15年前一样,然后它被允许(在我的教科书和我尝试的每个编译器中). (2认同)

Cam*_*ron 20

我已经看到它在函数有许多潜在的退出点时用作有用的模式,但无论函数如何退出,总是需要相同的清理代码.

它可以让烦人的if/else-if树更容易阅读,只要在到达出口点时必须中断,其余的逻辑后续内联.

此模式在没有goto语句的语言中也很有用.也许这就是原始程序员学习模式的地方.

  • 然后使用一个诚实,直接的goto而不是一个伪装的goto. (14认同)
  • 不使用goto是因为"耻辱"是货物崇拜节目的明确标志. (13认同)
  • 如果谨慎和本地使用,则很容易阅读.当它们是流量控制的主要形式并且跳过数百条线时,它们从后面得到了耻辱. (8认同)
  • 更不用说大量的清理代码是难闻的气味.这是C++,清理代码通常应该在RAII处理期间调用析构函数. (6认同)
  • 我更喜欢这种方式.它易于阅读,不带有goto的耻辱感. (4认同)
  • 这里有一些循环推理.一个需要这种构造的长期复杂的功能正是"goto"被认为是有害的.你告诉自己"哦,它只是那个",然后几个星期后你必须放一个特殊情况绕过清理代码.如果您在复杂的控制流程中遇到所有这些问题,那么您的第一直觉应该是重构代码并使其更加模块化. (2认同)

Bri*_*ung 12

我见过的代码一样,所以你可以使用break一个goto不爽.


Joh*_*ler 10

这只是while为了在goto tidy-up不使用goto这个词的情况下得到sematics的变态.

它的坏的形式,因为当你使用其他外内循环whilebreaks变得模糊给读者. "这应该转到退出吗?或者这只是为了打破内循环?"


Rob*_*ert 10

我认为写break代替更方便goto end.您甚至不必为标签想出一个名称,使意图更清晰:您不希望跳转到具有特定名称的标签.你想离开这里.

也有可能你需要大括号.所以这是do{...}while(false);版本:

do {
   // code
   if (condition) break; // or continue
   // more code
} while(false);
Run Code Online (Sandbox Code Playgroud)

如果您想使用,这就是您必须表达的方式goto:

{
   // code
   if (condition) goto end;
   // more code
}
end:
Run Code Online (Sandbox Code Playgroud)

我认为第一个版本的含义更容易掌握.此外,它更容易编写,更容易扩展,更容易翻译成不支持的语言goto等.


关于使用的最常提到的担忧break是它被严重伪装goto.但实际上break有更多的相似之处return:两个指令都跳出了一个代码块,与之相比,它的结构非常相似goto.然而,两个指令都允许代码块中的多个退出点,这有时会令人困惑.毕竟我会尝试寻求最明确的解决方案,无论具体情况如何.


AnT*_*AnT 7

这个技巧被程序员使用goto,他们太害羞而不能在代码中使用显式.上面代码的作者希望能够从代码中间直接跳转到"清理并返回"点.但他们不想使用标签和明确的goto.相反,他们可以使用break上述"假"循环体内部来达到同样的效果.


Dmi*_*try 7

这是一种非常普遍的做法.在Ç.我试着把它想象成你想以某种方式欺骗自己 "我没有使用goto".考虑一下,goto使用类似的东西也没有错.实际上它也会降低缩进程度.

尽管如此,我注意到,这种do..while循环往往会增长.然后他们得到ifs和elses内部,使代码实际上不是很可读,更不用说可测试了.

这些do..while通常是为了清理.无论如何,我宁愿使用RAII并从短期函数中提前返回.另一方面,C并没有像C++那样为您提供那么多便利,这是进行清理的最佳方法之一.do..while


Pet*_*ham 6

它看起来像一个C程序员.在C++中,自动变量具有用于清理的析构函数,因此在返回之前不应该有任何需要整理的东西.在C中,你没有这个RAII习语,所以如果你有共同的清理代码,你可以使用goto它,或者如上所述使用一次性循环.

与C++习语相比,它的主要缺点是,如果在体内抛出异常,它将无法整理.C没有例外,所以这不是问题,但它确实使它成为C++的坏习惯.


uli*_*ess 6

几种解释。第一个是通用的,第二个是特定于带有参数的 C 预处理器宏:

流量控制

我已经看到在普通 C 代码中使用了它。基本上,它是一个更安全的 goto 版本,因为您可以摆脱它并且所有内存都得到正确清理。

为什么goto-like 会是好的?好吧,如果您的代码几乎每一行都可以返回错误,但是您需要以相同的方式对所有代码做出反应(例如,在清理后将错误交给您的调用者),则通常更易读,以避免出现if( error ) { /* cleanup and error string generation and return here */ }as它避免了重复的清理代码。

但是,在 C++ 中,出于这个目的,您有例外 + RAII,所以我认为它是糟糕的编码风格。

分号检查

如果您在类似函数的宏调用后忘记了分号,则参数可能会以不希望的方式收缩并编译为有效的语法。想象一下宏

#define PRINT_IF_DEBUGMODE_ON(msg) if( gDebugModeOn ) printf("foo");
Run Code Online (Sandbox Code Playgroud)

这不小心被称为

if( foo )
    PRINT_IF_DEBUGMODE_ON("Hullo\n")
else
    doSomethingElse();
Run Code Online (Sandbox Code Playgroud)

“else”将被视为与 相关联gDebugModeOn,因此当foo是 时false,将发生与预期完全相反的情况。

为临时变量提供范围。

由于 do/while 有花括号,临时变量有一个明确定义的范围,它们无法逃脱。

避免“可能不需要的分号”警告

某些宏仅在调试版本中激活。您将它们定义为:

#if DEBUG
#define DBG_PRINT_NUM(n) printf("%d\n",n);
#else
#define DBG_PRINT_NUM(n) 
#endif
Run Code Online (Sandbox Code Playgroud)

现在,如果您在条件中的发布版本中使用它,它会编译为

if( foo )
    ;
Run Code Online (Sandbox Code Playgroud)

许多编译器认为这与

if( foo );
Run Code Online (Sandbox Code Playgroud)

这通常是不小心写的。所以你会收到警告。do{}while(false) 向编译器隐藏了这一点,并被它接受作为您真的不想在这里做任何事情的指示。{}

避免通过条件捕获行

上一个示例中的宏:

if( foo )
    DBG_PRINT_NUM(42)
doSomething();
Run Code Online (Sandbox Code Playgroud)

现在,在调试版本中,由于我们还习惯性地包含分号,因此编译得很好。但是,在发布版本中,这突然变成了:

if( foo )

doSomething();
Run Code Online (Sandbox Code Playgroud)

或更清晰的格式

if( foo )
    doSomething();
Run Code Online (Sandbox Code Playgroud)

这根本不是我们想要的。在宏周围添加 do{ ... }while(false) 会将缺少的分号变成编译错误。

这对 OP 意味着什么?

通常,您希望在 C++ 中使用异常进行错误处理,并使用模板而不是宏。但是,在非常罕见的情况下,您仍然需要宏(例如,使用标记粘贴生成类名时)或仅限于纯 C,这是一种有用的模式。