做{...} while(false)

时间:2010-02-22 20:50:02

标签: c++

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

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

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

 <tidy-up & return>
}

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

25 个答案:

答案 0 :(得分:163)

break可以do{...}while(false)

答案 1 :(得分:114)

很多人都指出它经常与休息一起使用,作为写“goto”的尴尬方式。如果它直接写在函数中,那可能就是真的。

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

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

答案 2 :(得分:24)

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

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

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

答案 3 :(得分:20)

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

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

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

答案 4 :(得分:12)

我见过这样的代码,因此您可以将break用作goto种类。

答案 5 :(得分:10)

这只是while的变形,以获取goto tidy-up的代码而不使用goto这个词。

这是糟糕的形式,因为当您在外部while内使用其他循环时,breaks对读者变得模糊不清。 “这应该转到退出吗?或者这只是为了突破内循环?”

答案 6 :(得分:9)

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

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

do {
   // code
   if (condition) break; // or continue
   // more code
} while(false);

如果你想使用goto

,这就是表达它的方式
{
   // code
   if (condition) goto end;
   // more code
}
end:

我认为第一版的含义更容易理解。此外,它更容易编写,更容易扩展,更容易翻译成不支持goto的语言等。


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

答案 7 :(得分:7)

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

答案 8 :(得分:7)

这是一种非常普遍的做法。在 C 。我试着把它想象成你想要以“我不使用goto”的方式骗自己。考虑一下,同样使用goto没有任何问题。实际上它也会降低缩进程度。

尽管如此,我注意到,这个do..while循环往往会增长。然后他们将ifelse放在里面,使代码实际上不易读,更不用说可测试了。

那些do..while通常用于执行清理。无论如何,我希望从函数中使用 RAII 早期返回。另一方面, C 并不像 C ++ 那样为您提供便利,使do..while成为进行清理的最佳方法之一。< / p>

答案 9 :(得分:6)

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

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

答案 10 :(得分:4)

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

流量控制

我在普通C代码中看到过这种情况。基本上,它是一个更安全的goto版本,因为你可以突破它并且所有内存都得到了适当的清理。

为什么会goto - 好像?好吧,如果你有代码几乎每一行都可以返回错误,但是你需要以相同的方式对所有这些代码作出反应(例如,在清理后将错误交给你的调用者),它通常更具可读性以避免{ {1}}因为它避免了重复的清理代码。

但是,在C ++中,你有异常+ RAII用于这个目的,所以我认为它编码风格不好。

分号检查

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

if( error ) { /* cleanup and error string generation and return here */ }

这被意外地称为

#define PRINT_IF_DEBUGMODE_ON(msg) if( gDebugModeOn ) printf("foo");

“else”将被视为与if( foo ) PRINT_IF_DEBUGMODE_ON("Hullo\n") else doSomethingElse(); 相关联,因此当gDebugModeOnfoo时,与预期相反的情况将会发生。

为临时变量提供范围。

由于do / while具有花括号,临时变量具有明确定义的范围,因此无法转义。

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

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

false

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

#if DEBUG
#define DBG_PRINT_NUM(n) printf("%d\n",n);
#else
#define DBG_PRINT_NUM(n) 
#endif

许多编译器将此视为与

相同
if( foo )
    ;

通常是偶然写的。所以你得到一个警告。 do {} while(false)从编译器中隐藏它,并被它接受,表明你真的想在这里做什么。

避免通过条件捕获行

上一个示例中的宏:

if( foo );

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

if( foo )
    DBG_PRINT_NUM(42)
doSomething();

或者格式更清晰

if( foo )

doSomething();

这根本不是什么意思。在宏周围添加do {...} while(false)会将丢失的分号变为编译错误。

这对OP意味着什么?

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

答案 11 :(得分:2)

这很简单:显然你可以随时使用break语句跳出假循环。此外,do块是一个单独的范围(也可以仅使用{ ... }实现)。

在这种情况下,使用RAII可能是一个更好的主意(当函数结束时,对象会自动正确地破坏)。另一个类似的构造是goto - yes, I know it's evil的使用,但它可以用来像这样有共同的清理代码:

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

 <main code for function using "goto error;" if something goes wrong>

 <tidy-up in success case & return>

 error:

 <commmon tidy-up actions for error case & return error code or throw exception>
}

(顺便说一句:在Lua中使用do-while-false构造来提出缺少的continue语句。)

答案 12 :(得分:2)

我认为这样做是为了使用breakcontinue语句。某种“转到”代码逻辑。

答案 13 :(得分:2)

也许它被使用,以便break可以在内部使用以在任何时候中止执行更多代码:

do {
    if (!condition1) break;
    some_code;
    if (!condition2) break;
    some_further_code;
    // …
} while(false);

答案 14 :(得分:2)

许多回答者都说明了do{(...)break;}while(false)的原因。我想通过另一个现实生活中的例子补充图片。

在下面的代码中,我必须根据operation指针指向的地址设置枚举器data。因为switch-case只能在标量类型上使用,所以我这样做的效率很低

if (data == &array[o1])
    operation = O1;
else if (data == &array[o2])
    operation = O2;
else if (data == &array[on])
    operation = ON;

Log("operation:",operation);

但是由于Log()和其余的代码重复了任何选择的操作值,我正在徘徊如何在已经发现地址时跳过其余的比较。这就是do{(...)break;}while(false)派上用场的地方。

do {
    if (data == &array[o1]) {
        operation = O1;
        break;
    }
    if (data == &array[o2]) {
        operation = O2;
        break;
    }
    if (data == &array[on]) {
        operation = ON;
        break;
    }
} while (false);

Log("operation:",operation);

有人可能想知道为什么他不能在break声明中对if做同样的事情,例如:

if (data == &array[o1])
{
    operation = O1;
    break;
}
else if (...)

break仅与最近的循环或切换进行互动,无论是forwhile还是do .. while类型,所以很遗憾那不会起作用。

答案 15 :(得分:1)

作者多大了?

我问,因为我曾经遇到过一些实时的Fortran代码,这些代码在80年代后期出现过。事实证明,这是在没有它们的操作系统上模拟线程的一种非常好的方法。你只需将整个程序(你的调度程序)放在一个循环中,然后逐个调用你的“线程”例程。线程例程本身就是循环,它会迭代直到其中一个条件发生(通常一个是一定数量的时间已经过去了。它是“合作多任务处理”,因为各个线程都要经常放弃CPU,所以其他线程不会被饿死。你可以嵌套循环子程序调用来模拟线程优先级频带。

答案 16 :(得分:1)

除了已经提到的'goto示例'之外,do ... while(0)成语有时在宏定义中用于在定义中提供括号,并且仍然让编译器使用添加半冒号来宏调用的结束。

http://groups.google.com/group/comp.soft-sys.ace/browse_thread/thread/52f670f1292f30a4?tvc=2&q=while+(0)

答案 17 :(得分:0)

有些程序员更喜欢只从他们的功能中退出/返回。使用虚拟do {....} while(false);允许您在完成后“断开”虚拟循环并仍然只有一次返回。

我是一个java编码器,所以我的例子就像是

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class p45
{
    static List<String> cakeNames = Arrays.asList("schwarzwald torte", "princess", "icecream");
    static Set<Integer> forbidden = Stream.of(0, 2).collect(Collectors.toSet());

    public static  void main(String[] argv)
    {
        for (int i = 0; i < 4; i++)
        {
            System.out.println(String.format("cake(%d)=\"%s\"", i, describeCake(i)));
        }
    }


    static String describeCake(int typeOfCake)
    {
        String result = "unknown";
        do {
            // ensure type of cake is valid
            if (typeOfCake < 0 || typeOfCake >= cakeNames.size()) break;

            if (forbidden.contains(typeOfCake)) {
                result = "not for you!!";
                break;
            }

            result = cakeNames.get(typeOfCake);
        } while (false);
        return result;
    }
}

答案 18 :(得分:0)

在这种情况下,我使用

switch(true) {
   case condution1:
      ...
      break;
   case condution2:
      ...
      break;
}

答案 19 :(得分:0)

许多人已经说明了这个结构与goto之间的相似性,并表达了对goto的偏好。也许这个人的背景包括一个环境,其中goto被编码指南严格禁止?

答案 20 :(得分:0)

我使用的是Adobe InDesign SDK,而InDesign SDK示例几乎包含了所有这样的函数。事实上,这个功能通常很长。您需要在哪里执行QueryInterface(...)以从应用程序对象模型中获取任何内容。因此,通常每个QueryInterface后跟if都不顺利,中断。

答案 21 :(得分:0)

我同意大多数关于使用的海报作为一个轻微伪装的goto。宏也被提到是在风格中编写代码的潜在动机。

我还看到在混合C / C ++环境中使用的这个构造是一个穷人的例外。带有“break”的“do {} while(false)”可用于跳过代码块的末尾,如果在循环中遇到通常需要异常的东西。

我还了解了在商店中使用的这种构造,其中强制执行“单功能返回”的意识形态。同样,这取代了明确的“转到” - 但动机是避免多个返回点,而不是“跳过”代码并继续在该函数内实际执行。

答案 22 :(得分:0)

这是一种模仿GOTO的人为方式,因为这两者实际上是相同的:

// NOTE: This is discouraged!
do {
    if (someCondition) break;
    // some code be here
} while (false);
// more code be here

// NOTE: This is discouraged, too!
if (someCondition) goto marker;
// some code be here
marker:
// more code be here

另一方面,这两个都应该用if s来完成:

if (!someCondition) {
    // some code be here
}
// more code be here

虽然如果只是将一长串前向 - GOTO s转换为嵌套if,嵌套可能会有点难看。但真正的答案是适当的重构,而不是模仿古老的语言结构。

如果你拼命试图用GOTO s音译成算法,你可以用这个成语来做。然而,这肯定是非标准的,并且是一个很好的指标,表明你并没有严格遵守该语言的预期习语。

我不知道任何类似C语言的地方do / while实际上是什么惯用解决方案。

你可能可以将整个混乱重构成更合理的东西,使其更具惯用性和更具可读性。

答案 23 :(得分:0)

我能想到的另一个原因是它装饰大括号,而我相信更新的C ++标准裸支撑不合适(ISO C不喜欢它们)。否则要安静像lint这样的静态分析器。

不确定为什么你想要它们,可能是变量范围,或者是调试器的优势。

请参阅Trivial Do While loop和来自C2的Braces are Good

澄清我的术语(我相信遵循标准用法):

裸露的大括号

init();
...
{
c = NULL;
mkwidget(&c);
finishwidget(&c);
}
shutdown();

空括号(NOP):

{}

e.g。

while (1)
   {}  /* Do nothing, endless loop */

if (finished)
{
     closewindows(&windows);
     freememory(&cache);
}

将成为

if (finished)
     closewindows(&windows);
freememory(&cache);

如果删除大括号,从而改变执行流程,而不仅仅是局部变量的范围。因此,不是'独立'或'赤裸'。

可以使用裸括号或块来表示可能是您希望标记的(内联)函数的任何代码段,但不包括那时的重构。

答案 24 :(得分:-1)

这很有趣。正如其他人所说,循环内部可能存在中断。我会这样做的:

while(true)
{
   <main code for function>
   break; // at the end.
}