1

I'm writing a set of functions that return a status enum class. I want to recognize whenever we hit an issue and stop the BigFunction.

Status BigFunction()
{
    Status status;

    status = function1();
    if (status != Status::SUCCESS)
        return status;

    status = function2();
    if (status != Status::SUCCESS)
        return status;

    // Do more

    return status;
}

Is there a 'neater' way to do this? It seems a little cumbersome and messy to have the if statement every time? Is there a way to get something like return_if_error function1() instead?

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • 4
    Related: [When and how should I use exception handling?](https://stackoverflow.com/q/4506369/11082165) (assuming you have control over the design of function1 and function2) – Brian61354270 Jan 13 '22 at 16:55
  • Make the check for error it's own function to avoid repetition, but otherwise welcome to error code returns. I'm not a fan. – sweenish Jan 13 '22 at 16:55

2 Answers2

3

That's why exceptions exist. If you have no control over the behavior of function1 and function2, you could create a helper function (e.g. check) that throws on status != Status::SUCCESS:

void check(Status status) {
    if (status != Status::SUCCESS)
        throw std::runtime_error("Status was not success");
}

Then use it instead of the if statement and wrap everything in try catch:

try {
    check(function1());
    check(function2());
} catch (const std::runtime_error &e) {
    // error handling
}
eike
  • 1,314
  • 7
  • 18
  • 3
    The `status` can be bundled into a `status_error` object that is derived from `runtime_error`, allowing that status to be further used (e.g., logged) as needed. If non-success status is commonly expected, then exceptions may be the wrong tool and the many if-sniff tests might be the best option. – Eljay Jan 13 '22 at 17:00
  • Thanks, that makes sense. If I do have control over function1 and function2, that's when I should be using asserts (in a similar way to the throwing std:runtime errors? – Christopher Hickey Jan 13 '22 at 17:03
  • @ChristopherHickey Either asserts or just throwing the exception directly so that no check function is needed. That depends on what you want to do after an error occurred. The thread Brian linked to in his comment contains plenty of information about this. – eike Jan 13 '22 at 17:04
  • 1
    @Eljay I left out a custom exception class for the sake of brevity, but in practice this of course makes a lot of sense. Any information about the failure that is relevant to the post-failure code should be stored in the exception. – eike Jan 13 '22 at 17:06
2

On most compilers (I do not know any exceptions), exceptions are zero overhead when not thrown, but when exception is thrown unwinding stack is pretty slow (I've seen source claiming that penalty is x40 times).

For that reason many developers prefer use exceptions only for exceptional cases ;) (for things that hardly ever happen).

There is other way to meet your requirement, just use && operator and provide tool which can store status:

struct StatusKeeper {
    bool update(Status newStatus) {
        status = newStatus;
        return status == Status::SUCCESS;
    }
    
    bool operator()(Status newStatus) {
       return update(newStatus);
    }

    Status get() const {
       return status;
    }

    operator Status () const {
       return status;
    }

private:
    Status status = Status::SUCCESS;
};

Status BigFunction()
{
    StatusKeeper status;

    status(function1())
    && status(function2())
    && status(function3());

    // or more verbose, but more readable:
    status.update(function1())
    && status.update(function2())
    && status.update(function3());
    
    return status;
}

Now second argument for operator && is evaluated only if first argument was evaluated to true.

There is also possibility to use C macros for that purpose, but IMHO in C++ the less macros then better.

Marek R
  • 32,568
  • 6
  • 55
  • 140