Properly terminating program. Using exceptions
Asked Answered
H

5

31

Question:

Is using exceptions the proper way to terminate my program if all I want is to display an error message and close (accounting that I may be deep in the program)? Or can I just explicitly call something like exit(EXIT_FAILURE) instead?

What I'm Currently Doing:

I'm working on a game project and am trying to figure out the best way to terminate the program in the case of an error that calls for such an action. For example, in the case the textures can't be loaded I display an error message and terminate the program.

I'm currently doing this with exceptions like so:

int main()
{
   Game game;
   try
   {
       game.run();
   }
   catch (BadResolutionException & e)
   {
       Notification::showErrorMessage(e.what(), "ERROR: Resolution");
       return 1;
   }
   catch (BadAssetException & e)
   {
       Notification::showErrorMessage(e.what(), "ERROR: Assets");
       return 1;
   }
   catch (std::bad_alloc & e)
   {
       Notification::showErrorMessage(e.what(), "ERROR: Memory");
       return 1;
   }
   return 0;
}

All but bad_alloc are my own defined exceptions derived from runtime_error.

I don't need any manual resource cleanup and I'm using std::unique_ptr for any dynamic allocation. I just need to display the error message and close the program.

Research/Alternatives to Exceptions:

I've looked up a lot of posts on SO and other places and have seen others say anything from don't use exceptions, to use exceptions but your using them wrong. I've also looked up explicitly calling something like exit().

Using exit() sounds nice but I read it won't go back through the call stack up to main cleaning everything up (if I can find this again I'll post the link). Additionally, according to http://www.cplusplus.com/reference/cstdlib/exit/ this should not be used if multiple threads are active. I do expect to be creating a second thread for a short time at least once, and an error could occur in that thread.

Not using exceptions was mentioned in some replies here in relation to games https://gamedev.stackexchange.com/questions/103285/how-industy-games-handle-their-code-errors-and-exceptions

Use exceptions was discussed here: http://www.quora.com/Why-do-some-people-recommend-not-using-exception-handling-in-C++

There are a number of other sources I've read but those were the most recent I looked at.

Personal Conclusion:

Due to my limited experience of working with error handling and using exceptions, I'm not sure if I'm on the right track. I've chosen the route of using exceptions based on the code I posted above. If you agree that I should tackle those cases with exceptions, am I using it correctly?

Harri answered 27/8, 2015 at 19:22 Comment(6)
Look at std::exit before you spend much time on exit. It does a better job of clean up in a RAII world.Almanac
@Almanac In what way does it do a better job? It doesn't even unwind the stack...Potentiality
Well, even though you didn't mention it, you shouldn't call exit() in the middle of class or library code. #22843689 So if you're thinking of doing something like that, don't.Grisly
Having a library: Never use anything exiting the program, unless it is severe. Similar applies to a standalone executable.Embodiment
Any time there is a question about when to use exceptions it inevitably is closed. It's a legit question. Stop trying to close these!Potentiality
@Potentiality No it doesn't, but exit does sweet bugger all, so getting the statics and thread locals cleaned up is a vast improvement.Almanac
C
20

It's generally considered good practice to let all exceptions propagate through to main. This is primarily because you can be sure the stack is properly unwound and all destructors are called (see this answer). I also think it's more organised to do things this way; you always known where your program will terminate (unless the program crashes). It's also facilitates more consistent error reporting (a point often neglected in exception handling; if you can't handle the exception, you should make sure your user knows exactly why). If you always start with this basic layout

int main(int argc, const char **argv)
{
    try {
         // do stuff
         return EXIT_SUCCESS;
    } catch (...) {
        std::cerr << "Error: unknown exception" << std::endl;
        return EXIT_FAILURE;
    }
}

then you won't go far wrong. You can (and should) add specific catch statements for better error reporting.

Exceptions when multithreading

There are two basic ways of executing code asynchronously in C++11 using standard library features: std::async and std::thread.

First the simple one. std::async will return a std::future which will capture and store any uncaught exceptions thrown in the given function. Calling std::future::get on the future will cause any exceptions to propagate into the calling thread.

auto fut = std::async(std::launch::async, [] () { throw std::runtime_error {"oh dear"}; });
fut.get(); // fine, throws exception

On the other hand, if an exception in a std::thread object is uncaught then std::terminate will be called:

try {
    std::thread t {[] () { throw std::runtime_error {"oh dear"};}};
    t.join();
} catch(...) {
    // only get here if std::thread constructor throws
}

One solution to this could be to pass a std::exception_ptr into the std::thread object which it can pass the exception to:

void foo(std::exception_ptr& eptr)
{
    try {
        throw std::runtime_error {"oh dear"};
    } catch (...) {
        eptr = std::current_exception();
    }
}

void bar()
{
    std::exception_ptr eptr {};

    std::thread t {foo, std::ref(eptr)};

    try {
        // do stuff
    } catch(...) {
        t.join(); // t may also have thrown
        throw;
    }
    t.join();

    if (eptr) {
        std::rethrow_exception(eptr);
    }
}

Although a better way is to use std::package_task:

void foo()
{
    throw std::runtime_error {"oh dear"};
}

void bar()
{
    std::packaged_task<void()> task {foo};
    auto fut = task.get_future();

    std::thread t {std::move(task)};
    t.join();

    auto result = fut.get(); // throws here
}

But unless you have good reason to use std::thread, prefer std::async.

Carte answered 27/8, 2015 at 19:42 Comment(3)
Perfect. I'm feeling a lot more comfortable continuing on with what I'm doing now. Though, I do have a quick question regarding using exceptions like this. If I did create a new thread for a short time (I haven't done this before, just experimenting) and I throw an exception from that thread would this still work? (if it's not too far off scope from this discussion)Harri
@AlwaysCoding See update. I'm not 100% sure this is the best approach however.Carte
Take a look at std::packaged_task for transporting exceptions over thread boundaries. No need to reinvent the wheel here.Reneareneau
P
8

There's nothing wrong with catching unrecoverable errors and shutting down your program this way. In fact, it's how exceptions should be used. However, be careful not to cross the line of using exceptions to control the flow of your program in ordinary circumstances. They should always represent an error which cannot be gracefully handled at the level the error occurred.

Calling exit() would not unwind the stack from wherever you called it. If you want to exit cleanly, what you're already doing is ideal.

Potentiality answered 27/8, 2015 at 19:39 Comment(1)
Ok that's good to know. In fact that was my initial worry about exit() is it didn't seem safe.Harri
T
6

You have already accepted an answer, but I wanted to add something about this:

Can I just explicitly call something like exit() instead?

You can call exit, but (probably) shouldn't.

std::exit should be reserved for situations where you want to express "exit right now!", not simply "application has nothing left to do".

As an example, if you were to write a controller for a laser used in cancer treatments, your first priority in case something went wrong would be to shut down the laser and call std::exit - or possibly std::terminate (to ensure any side effects of a hanging, slow or crashing application do not kill a patient).

Similar to how exceptions should not be used for controlling application flow, exit should not be used to stop the application in normal conditions.

Test answered 27/8, 2015 at 20:44 Comment(0)
B
3

Is using exceptions the proper way to terminate my program if all I want is to display an error message and close (accounting that I may be deep in the program)?

Yes. This is the reason for using exceptions. An error occurred deep down in the code and something at a higher level will handle it. In your case, at the highest level.

There are arguments for/against exceptions vs. error codes, and this is a good read:

Exceptions or error codes

Can I just explicitly call something like exit() instead?

You can, but you may end up duplicating your logging code. Also, if in the future you decide that you want to handle an exception differently you will have to change all your exit calls. Imagine you wanted a different message, or to fall back on an alternative process.

Another similar question:

Correct usage of exit() in c++?

You also have a flaw in your approach as you don't handle all (C++) exceptions. You want something like this:

int main()
{
    Game game;
    try
    {
        game.run();
    }
    catch (BadResolutionException & e)
    {
        Notification::showErrorMessage(e.what(), "ERROR: Resolution");
        return 1;
    }
    catch (BadAssetException & e)
    {
        Notification::showErrorMessage(e.what(), "ERROR: Assets");
        return 1;
    }
    catch (std::bad_alloc & e)
    {
        Notification::showErrorMessage(e.what(), "ERROR: Memory");
        return 1;
    }
    catch (...)
    {
        // overload?
        Notification::showErrorMessage("ERROR: Unhandled");
        return 1;
    }

    return 0;
}

If you don't handle all* exceptions you could have the game terminate without telling you anything useful.

You can't handle ALL exceptions. See this link:

C++ catching all exceptions

Baguio answered 27/8, 2015 at 20:39 Comment(0)
F
2

From the documentation:

[[noreturn]] void exit (int status); Terminate calling process Terminates the process normally, performing the regular cleanup for terminating programs.

Normal program termination performs the following (in the same order): Objects associated with the current thread with thread storage duration are destroyed (C++11 only). Objects with static storage duration are destroyed (C++) and functions registered with atexit are called. All C streams (open with functions in ) are closed (and flushed, if buffered), and all files created with tmpfile are removed. Control is returned to the host environment.

Note that objects with automatic storage are not destroyed by calling exit (C++).

If status is zero or EXIT_SUCCESS, a successful termination status is returned to the host environment. If status is EXIT_FAILURE, an unsuccessful termination status is returned to the host environment. Otherwise, the status returned depends on the system and library implementation.

For a similar function that does not perform the cleanup described above, see quick_exit.

Fatty answered 27/8, 2015 at 20:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.