C++ Error Handling – Best Practices for Handling Exceptions

cerror handlingerrorsexceptions

So, in the recent weeks I delved into C++ programming, and I programmed some things in SDL. Doing so, you always have to deal with a lot of (ugly) C++ code, which looks more like C than C++.
One thing I noticed I when programming with SDL as opposed to college programming, that I do a lot of procedural programming. The reason for this is, that most sample SDL code I look at is structured like this, e.g.

bool init(…)
{
    bool success = true;
    thing_A = make_thing_A();
    if (thing_A.is_valid() == false
    {
        success = false;
        std::cout << "Something went wrong" << std::endl;
    }
    …
    return success;
}

…like this. The cleanup would occur in the caller.
However this only works with functions, that don't need to return other variables otherwise. And this creates an ugly interface, where you never, if you can write something like if (!init()), which makes you having to consult the documentation.
I never know if this is proper programming practice. Another approach would be to:

void init(…)
{
    try
    {
        thing_A = make_thing_A();
        if (thing_A.is_valid() == false
        {
            throw ;
        }
        …
    catch (std::exception &e)
    {
        std::cout << "Something went wrong" << std::endl;
         cleanup();
    }
}

But then I read, I shouldn't put this try-catch-routine into the function, but rather into the caller:

void init(…)
{

    thing_A = make_thing_A();
    if (thing_A.is_valid() == false
    {
        throw ;
    }
}

int main(void)
{
    try
    {
        init(…);
    }
    catch (std::exception &e)
    {
        std::cout << "Something went wrong" << std::endl;
        cleanup();
    }
}

At the moment this is my number one issue, when writing software, I just don't what to do. I think the cleanest solution is the second, because it encapsulates the complete logic into the callee and it implements DRY, because you don't have to put the cleanup logic into every caller. Is that right? I've never heard an absolute opinion on this.

EDIT: I'm not necessarily specifically talking about SDL2, but more generally. E.g. I know there is a consensus, that you should prefer std::unique_ptr over std::auto_ptr, but when dealing with error handling I see so many approaches: errno, return values, exceptions, booleans, but I don't which one is the go-to approach, which one you should avoid, in which case it might be a good idea to do approach XYZ, etc.

Best Answer

There are two methods of dealing with errors that I am aware of.

  1. Through the return value of a function.
  2. By raising exceptions.

They can be mixed when adequate care is taken into designing your application.

There are pros and cons to both approaches.

Dealing with error as a return value

The return type of the function can be bool where a return value of true indicates success and false indicates failure.

You can also use int as return type where a return value of 0 would indicate success and any non-zero value would indicate failure. This gives a bit more flexibility to indicate the type of error. The returned value could be an error code. You could map that code to an error string and hope to inform the user about the error in a more meaningful way.

Pros:

The pros of using the return value as an error code is that it is easy to debug the code by just inspecting the code. It is easier to follow the flow of execution.

Cons:

The cons of using the return value as an error code is that every caller must deal with the return value. If a call ignores a return value, it is a source of a bug. They should not ignore that call since they might not have gotten what they hoped to get.

Dealing with error by raising an exception

Pros:

Dealing with error by raising an exception opens up a whole lot more flexibility than you could expect from returning an error code.

You could throw any type of object. It could be a fundamental type, a std::string, any of exceptions defined in the standard library, or any one of your classes.

Your code can be simpler. You don't have to worry about catching exceptions everywhere. You could decide the level at which exceptions must be dealt with.

Cons:

The price of the added flexibility is that your code must be prepared to deal with any exceptions that could get thrown. Also, a lot more care must go into deciding when and how to deal with the exceptions.

You have to be always cognizant of the fact that any uncaught exception will terminate your program.

Concluding Remarks

If you are working in a team, make sure that the method you choose seems appropriate to other members of your team.

If you are working alone in a project, do what makes most sense to you.

Related Topic