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