1

I use an error codes as return values. But each time I call a function, I use an if-else to check the return value is a good result. For example,

int v1,v2,v3,v4;
v1 = func1();
if(v1 != OK){
    // do somethings
}
else{
    v2 = func2();
    if(v2!=OK){
        // do somethings
    }
    else{
        v3 = func3();
        if(v3!=OK){
            // do somethings
        }
        else{
        v4 = func4();
           if(v4!=OK){
               // do somethings
           }
           else{
               .....
           }
        }
    }
}
Cuiry
  • 13
  • 2
  • 1
    You can use exceptions. It will still need multiple blocks though. – Nevus Apr 17 '19 at 01:21
  • There probably is some logic design flaw if you really need this many nested things... – Omid CompSCI Apr 17 '19 at 01:23
  • 2
    `if(func1() != OK) { /* do something */...; return; }`. And after that you don't need `else`. You may need to move it to a function if you have some code after that (so that `return` works properly). –  Apr 17 '19 at 01:26

4 Answers4

1

You can wrap the code into another function to avoid nested if-else blocks.

void foo() {

    int v = func1();
    if (v != OK) {
        // do somethings
        return;
    }

    v = func2();
    if (v != OK) {
        // do somethings
        return;
    }

    v = func3();
    if (v != OK) {
        // do somethings
        return;
    }

    v = func4();
    if (v != OK) {
        // do somethings
        return;
    }

    .....

}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
1

If prototypes are identical, you might use loop:

 using f_ptr = int (*)();
 using error_f_ptr = void (*)();
 std::pair<f_ptr, error_f_ptr> funcs[] = {
     {&func1, &err1},
     {&func2, &err2},
     {&func3, &err3},
     {&func4, &err4}
 };

 for (const auto& p : funcs) {
      const int v = p.first();

      if (v != OK) {
          p.second();
          return FAIL;
      }
 }
 return OK;
Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

Here's a few patterns I've used in the absence of C++ exceptions. In all cases, the intent is to have a single return point in the code. (Because a single point of return often makes for more readable and maintainable code - not always, but it's a good thing to strive for.) Also, all solutions are great when you take advantage of RAII (let the destructors of local variables do all the cleanup work for you).

Classic approach is the "triangle pattern":

int MyCode()
{
    int v1, v2, v3, v4;
    int result = FAIL;

    v1 = func1();
    if (v1 == OK)
    {
       v2 = func2();
       if (v2 == OK)
       {
           v3 = func3();
           if (v3 == OK)
           {
               v4 = func4();
               if (v4 == OK)
               {
                  result = OK;
               }
               else
               {
                  // handle func4 failure
               }
           }
           else
           {
               // handle func3 failure
           }
       }
       else
       {
           // handle func2 failure
       }
    else
    {
        // handle func1 failure
    }

    if (result != OK)
    {
        // general cleanup
    }
}

For the above, if you can take advantage of RAII or put most of the "handle cleanup code" in the // general cleanup block at the end, you won't have to write redundant code in each nested else clause - possible excluding the else clauses altogether.

Another variation, I've enjoyed using is the "chained success check":

int MyCode()
{
    int result = OK;

    result = func1();
    if (result == OK)
    {
       // additional code to handle result of func1 before invoking func2
       result = func2();
    }

    if (result == OK)
    {
       // additional code to handle result of func2 before invoking func3
       result = func3();
    }
    else
    {
        // special error handling for func3 failing
    }

    if (result == OK)
    {
        result = func4();
    }

    if (result == OK)
    {
       // handle success case, if anything            
    }
    else
    {
        // general error handling and function cleanup goes here
    }
    return result;
}

Looks weird at first, but when you code in the above style, it allows you to see the expected flow of the code (when success is the norm). You might observe that it's a lot of redundant checks for result==OK for when an error case happens. In a release build, the compiler can optimize this out.

Another variation of the chained success check is to use... wait for it... don't freak out.... a goto macro specifically for jumping to the end of the function (crowd gasps) on failure. But look at how simple it makes the code look:

#define JUMP_ON_ERROR(expr) {result = (expr); if (result != OK) goto Cleanup;}

int MyCode()
{
    int result = OK;

    JUMP_ON_ERROR(func1());
    JUMP_ON_ERROR(func2());
    JUMP_ON_ERROR(func3());
    JUMP_ON_ERROR(func4());
Cleanup:

     if (result == OK)
     {
         // handle success
     }
     else
     {
         // handle failure
     }
     return result;
}
selbie
  • 100,020
  • 15
  • 103
  • 173
0

Consider using try catch blocks and throw exceptions in your funcs instead of returning an error value

int v1, v2, v3, v4;
try
{
    v1 = func1();
    v2 = func2();
    v3 = func3();
    v4 = func4();
}
catch (Func1Exception e)
{
    //handle Func1Exception
}
catch (Func2Exception e)
{
    //handle Func2Exception
}
catch (Func3Exception e)
{
    //handle Func3Exception
}
catch (Func4Exception e)
{
    //handle Func4Exception
}

See this and this

Rufus
  • 5,111
  • 4
  • 28
  • 45