Poor Man's Exception

Let's say we are performing a sequence of operations:

OpA();
OpB();
OpC();

Now, let's assume each operation can fail, and returns false if it does (in reality, the condition may be more complicated). And if an operation fails, we want to abort and skip all subsequent operations.

The most straight-forward way to implement this is by nesting if statements:

if(OpA()) {
    if(OpB()) {
        if(OpC()) {
            …
        }
    }
}

If the control flow of the operations is more complex, nested ifs do not work so well anymore:

OpA();
if( cond() ) OpB();
OpC();
…

would turn into

if(OpA()) {
    if(!cond() || OpB()) {
        if(OpC()) {
            …
        }
    }
}

The condition in the second if handles the failure as well as the regular control flow of the operations. Mixing these two concerns is less than ideal. If we throw a loop into the mix, we cannot make the transformation at all:

OpA();
while( cond() ) OpB();
OpC();
…

has no straightforward aborting equivalent with nested ifs.

The classic solution to this problem are exceptions:

try {
    if(!funcA()) throw abort();
    while( cond() ) {
        if(!funcB()) throw abort();
    }
    if(!funcC()) throw abort();
    …
} catch(abort const&) {}

Is this the best we can do?

It does have some disadvantages. One is performance: exceptions as they are implemented in today's C++ compilers are slow when thrown. Another is that for the reader of the code, there is no guarantee that the throw abort()s are the only source of abort exceptions. They could come from inside OpA, OpB or OpC. To assure our reader that this is not the case, we must declare abort locally:

struct abort{};
try {
    if(!OpA()) throw abort();
    while( cond() ) {
        if(!OpB()) throw abort();
    }
    if(!OpC()) throw abort();
    …
} catch(abort const&) {}

Now the reader still has to ensure that we did not slip in a different exception somewhere that would not be caught by the catch:

struct abort{};
try {
    if(!OpA()) throw abort();
    while( cond() ) {
        if(!OpB()) throw abort2();
    }
    if(!OpC()) throw abort();
    …
} catch(abort const&) {}

If the body of the catch is empty (and only then), there is an alternative that has none of these problems: If the operations are the only thing happening in a function, we can use returns:

void operations() noexcept {
    if(!OpA()) return;
    while( cond() ) {
        if(!OpB()) return;
    }
    if(!OpC()) return;
…
}

operations is declared noexcept so it is evident that returns are the only codepaths exiting the function. And returns all return to the same place right outside the function, unlike exceptions, which can be caught in different places.

If the operations are not already isolated in a function, we can wrap them into an immediately executing lambda:

[&]() noexcept {
    if(!OpA()) return;
    while( cond() ) {
        if(!OpB()) return;
    }
    if(!OpC()) return;
    …
}();

I call this the poor man's exception. For certain situations, it is actually a good solution!

— by Arno Schödl

Do you have feedback? Send us a message at devblog@think-cell.com !

Sign up for blog updates

Don't miss out on new posts! Sign up to receive a notification whenever we publish a new article.

Just submit your email address below. Be assured that we will not forward your email address to any third party.

Please refer to our privacy policy on how we protect your personal data.

Share