2

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.

hgiesel
  • 771
  • 1
  • 7
  • 12
  • What is an "absolute opinion" lol – Lightness Races in Orbit Feb 20 '16 at 21:09
  • 2
    Why not wrap these things in C++ classes that implement RAII so you know each one will get the proper cleanup no matter what, and your try/catch in main can just log the exception message without worrying which cleanup functions it's supposed to invoke? (or does SDL really have just one cleanup function for everything?) – Ixrec Feb 20 '16 at 21:16
  • 1
    Yeah or just use one of the many C++ SDL bindings that already exist: http://stackoverflow.com/q/7695852/560648 – Lightness Races in Orbit Feb 20 '16 at 21:20
  • "However this only works with functions, that don't need to return other variables otherwise." Well, no, I'm commonly using two ways (on an embedded system, exceptions undesired): Using output parameters (by reference), or a template class that carries the success state (either bool or some error code depending on implementation) plus the result as payload in the success case. The latter one has become my favourite over the last months. And of course you can always return pairs/tuples, struct or class objects which also contain the state. – Murphy Feb 20 '16 at 21:28
  • @Murphy Can you show me some of the code you've written (maybe you uploaded it somewhere like GitHub), I'd be really interested in seeing that. – hgiesel Feb 20 '16 at 21:42
  • @PreferenceBean Of course there is no such thing as an absolute opinion, I'm talking more of a consensus, like, *you should do it like this* – hgiesel Feb 20 '16 at 21:43
  • @henrikgiesel You mean the template class, I guess? Not before monday, the code is @work. But it's rather simple, I took the idea from a more modern language I recently read about, but can't remember which it was. You just need a constructor for the error value (FAIL case), another for the OK case taking the result, and some getters for state and the payload (`bool isOk() const`, `const T& data() const`, `ErrorCode error() const`); you get the idea... – Murphy Feb 20 '16 at 21:49
  • I've finally had the chance to sketch that pattern in more detail in [another answer](https://softwareengineering.stackexchange.com/questions/359720/return-null-in-case-of-success/359722#359722). – Murphy Oct 25 '17 at 14:50

2 Answers2

4

The rule I use for error handling is: handle the error where you can do something about it.

If the way to handle the error is to log it, and you cannot use exceptions, it can make sense to do it as soon as possible, because that's where you have all the information you need, and you have no simple mechanisms to pass all that information around.

If you can use exceptions, you have more flexibility as to where you can handle the errors. In this case, I prefer to let the error bubble up until a more appropriate level, and I would prefer your last example.

However, if you can use exceptions, there is no reason to have a separate init function, and you'd be better off doing that work in the constructor. The reason for init methods is that, without exceptions, there is no way to tell if the constructor encountered a problem and you have a half-built object in your hands (which can be disastrous for your application). With exceptions, there's no risk of that, as you either have a successfully constructed object, or you have no object at all and you're in the catch block (or further up the call stack).

Iker
  • 865
  • 1
  • 6
  • 11
  • Can you maybe tell me a bit more, why you think it is a good approach to let the error bubble up? Let's say, instead of `init()` we had a `load_image()` function, and when it detects there is no `file.png` it wants to load, it throws. Isn't it more hassle to deal with this in the caller. I can see that you can implement different behaviors how to deal with this problem in the case of an exception, if you implement it in the caller. Is this the reason? – hgiesel Feb 20 '16 at 21:59
  • @henrikgiesel That's the main reason, yes. If you handle it in the function itself, you lose a lot of flexibility as to how you can react to the error. The more information you pass upwards, the greater the range of behaviors you can implement on the caller. – Iker Feb 20 '16 at 22:37
3

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.

R Sahu
  • 1,966
  • 10
  • 15
  • 1
    Another Con regarding exceptions: If you are on a(n embedded) device with very limited ressources the overhead may be a concern. Further reading: [The size complexity overhead isn't easily quantifiable but Eckel states an average of 5 and 15 percent](http://stackoverflow.com/questions/691168/how-much-footprint-does-c-exception-handling-add) while [the performance overhead seams to be negligible](https://isocpp.org/wiki/faq/exceptions#why-exceptions). – Murphy Feb 22 '16 at 20:24
  • @Murphy, thanks for the links. I wouldn't have thought about them since I develop applications only for desktops. – R Sahu Feb 22 '16 at 20:32
  • In this case this doesn't apply to you, and you don't have to think about that detail. Just wanted to put it here for the sake of completeness. – Murphy Feb 22 '16 at 20:42