do {…} while(false)

前端 未结 25 2299
小鲜肉
小鲜肉 2020-11-28 03:29

I was looking at some code by an individual and noticed he seems to have a pattern in his functions:

 function()
{
 

        
相关标签:
25条回答
  • 2020-11-28 03:51

    Maybe it’s used so that break can be used inside to abort the execution of further code at any point:

    do {
        if (!condition1) break;
        some_code;
        if (!condition2) break;
        some_further_code;
        // …
    } while(false);
    
    0 讨论(0)
  • 2020-11-28 03:51

    I work with Adobe InDesign SDK, and the InDesign SDK examples have almost every function written like this. It is due to fact that the function are usually really long. Where you need to do QueryInterface(...) to get anything from the application object model. So usually every QueryInterface is followed by if not went well, break.

    0 讨论(0)
  • 2020-11-28 03:52

    Several explanations. The first one is general, the second one is specific to C preprocessor macros with parameters:

    Flow control

    I've seen this used in plain C code. Basically, it's a safer version of goto, as you can break out of it and all memory gets cleaned up properly.

    Why would something goto-like be good? Well, if you have code where pretty much every line can return an error, but you need to react to all of them the same way (e.g. by handing the error to your caller after cleaning up), it's usually more readable to avoid an if( error ) { /* cleanup and error string generation and return here */ } as it avoids duplication of clean-up code.

    However, in C++ you have exceptions + RAII for exactly this purpose, so I would consider it bad coding style.

    Semicolon checking

    If you forget the semicolon after a function-like macro invocation, arguments might contract in an undesired way and compile into valid syntax. Imagine the macro

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

    That is accidentally called as

    if( foo )
        PRINT_IF_DEBUGMODE_ON("Hullo\n")
    else
        doSomethingElse();
    

    The "else" will be considered to be associated with the gDebugModeOn, so when foo is false, the exact reverse of what was intended will happen.

    Providing a scope for temporary variables.

    Since the do/while has curly braces, temporary variables have a clearly defined scope they can't escape.

    Avoiding "possibly unwanted semicolon" warnings

    Some macros are only activated in debug builds. You define them like:

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

    Now if you use this in a release build inside a conditional, it compiles to

    if( foo )
        ;
    

    Many compilers see this as the same as

    if( foo );
    

    Which is often written accidentally. So you get a warning. The do{}while(false) hides this from the compiler, and is accepted by it as an indication that you really want to do nothing here.

    Avoiding capturing of lines by conditionals

    Macro from previous example:

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

    Now, in a debug build, since we also habitually included the semicolon, this compiles just fine. However, in the release build this suddenly turns into:

    if( foo )
    
    doSomething();
    

    Or more clearly formatted

    if( foo )
        doSomething();
    

    Which is not at all what was intended. Adding a do{ ... }while(false) around the macro turns the missing semicolon into a compile error.

    What's that mean for the OP?

    In general, you want to use exceptions in C++ for error handling, and templates instead of macros. However, in the very rare case where you still need macros (e.g. when generating class names using token pasting) or are restricted to plain C, this is a useful pattern.

    0 讨论(0)
  • 2020-11-28 03:53

    It's simple: Apparently you can jump out of the fake loop at any time using the break statement. Furthermore, the do block is a separate scope (which could also be achieved with { ... } only).

    In such a situation, it might be a better idea to use RAII (objects automatically destructing correctly when the function ends). Another similar construct is the use of goto - yes, I know it's evil, but it can be used to have common cleanup code like so:

    <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>
    }
    

    (As an aside: The do-while-false construct is used in Lua to come up for the missing continue statement.)

    0 讨论(0)
  • 2020-11-28 03:56

    It looks like a C programmer. In C++, automatic variables have destructors which you use to clean up, so there should not be anything needed tidying up before the return. In C, you didn't have this RAII idiom, so if you have common clean up code, you either goto it, or use a once-through loop as above.

    Its main disadvantage compared with the C++ idiom is that it will not tidy up if an exception is thrown in the body. C didn't have exceptions, so this wasn't a problem, but it does make it a bad habit in C++.

    0 讨论(0)
  • 2020-11-28 03:56

    Many have already stated the similarity between this construct and a goto, and expressed a preference for the goto. Perhaps this person's background included an environment where goto's were strictly forbidden by coding guidelines?

    0 讨论(0)
提交回复
热议问题