• 11
• 9
• 10
• 9
• 11

C++: runtime error with message?

This topic is 438 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

Hi

I want a way to break/end the program at any time (such as throwing exception or something), in any method and show an error dialog, such as:

if(fatalCondition==true)
endProgram("FATAL ERROR. Unit-count can not be zero.");


What i do now is:

throw std::runtime_error(errorString);

But that doesnt show the message, it just says: Unhandled exception at 0x7524c54f in game.exe: Microsoft C++ exception: std::runtime_error at memory location 0x0018f29c..

It does take me to the location in the code where it's called, but I would prefer to get the message as well.

It can "freeze" the program for all I care, I just want a sure way to signal me (or any user) and give the message. I prefer to not have to backtrack and catch the exception, since I want to be flexible and be able to add this anywhere. Can I do it in another way?

Any tips?

Edited by suliman

Share on other sites

I could be mistaken, but the whole point of throwing an exception is that error can't be handled there, and thus sequential execution of the instruction pointer needs to be kicked up to the caller to catch/handle this exception.

That aside, is something like this an option?

//Syntax may not be exact
try
{
//some code that could flip the fatal condition boolean
if(fatalCondition==true) throw std::runtime_error(errorString);
} catch (std::runtime_error e) {
//MessageBox, or something to notify end user of application error
}

Edited by markypooch

Share on other sites

If you just want your program to die, there are a few options:

• abort() -- No cleanup or destructors, no calls to atexit() listeners. Basically the same result of the SIGABORT signal which some programs deal with in special ways.
• exit() -- Cleans up the program, closes resources, calls destructors, calls all the atexit() registered functions. Lets you return an error code to the OS.
• quick_exit() -- Partial cleanup of the program, some things destroyed, no calls to atexit() registered functions. Lets you return an error code to the OS.

In any event you can send a message to stdout, either with a newline at the end to trigger a flush or manually flush it, then call one of those.

If you want to show an error dialog, you'll need to show that dialog before calling the function. Once you call it your program will go through its cleanup and death.

Share on other sites

Annoyingly, assert() does not have a standard message-bearing equivalent at the moment, but MS has one:

https://msdn.microsoft.com/en-us/library/ezb1wyez.aspx

_ASSERT_EXPR(false == true, L"Logic is borken.");

This is in crtdbg.h, I think, but MS includes it in almost everything, so if you have any windows inclusions or standard library inclusions you can probably reach it.

It's obviously not portable, though. If you want a portable assert-with-message then there's a hacky method:

#include <Windows.h>
#include <cassert>

INT WinMain(HINSTANCE, HINSTANCE, PSTR, INT) {
assert((1 + 1 == 2) && "This shouldn't trigger.");
assert((1 + 1 == 3) && "This assertion failed.");
}


Share on other sites
While you'll often need platform-specific code, you can also detect if you're in a debugger and trigger a debugger breakpoint, which is a huge usability improvement over popping up a dialog indiscriminatelly.

On Win32, it's something like:

if (IsDebuggerPresent())
__debugbreak();
else
MyDialog(/*stuff*/);
I'd strongly recommend avoiding exceptions for your go-to error handling mechanism. They destroy forensic information (that is, the point you catch/detect them is well after the stack has been unwound and valuable debugging information is lost), though throwing an exception after logging or popping up a dialog can be a viable strategy (rare in games as disabling exceptions entirely is quite popular, but it certainly works).

That aside, is something like this an option?

Only if you think your game is performing too well. Just having try/catch blocks can have some perf consequences (e.g., see how Catch/doctest get huge compilation and runtime speed improvements when exceptions are disabled), and your failure case will be very slow indeed (though that's almost certainly not a perf-sensitive case, of course).

There's no reason to use exceptions here at all. A simple if-condition is all you need.

// logs and pops up a dialog, returns true if the assert should be ignored from here on out
bool handleFailedAssertion(char const* file, char const* line, char const* condition, string_view message);

// optionally formats the arguments given to assert, using a non-allocating string_buffer
string_buffer formatAssertMessage() { return {}; } // base case when no message is given
template <typename ...T> string_buffer formatAssertMessage(string_view format, T&&... args) { /*fancy string format stuff*/ }

extern bool global_ignoreAllAsserts; // set to true to disable all assert macros at runtime - might be set by dialog code

#define ASSERT(condition, ...) \
do{ \
static bool _ignored = global_ignoreAllAsserts; // will be set to try if this assert is toggled off at runtime
if (!_ignored && !(condition)) \
_ignored = handleFailedAssertion(__FILE__, __LINE__, #condition, formatAssertMessage(__VA_ARGS__)); \
}while(false);
There's quite a bit more you can do there to improve that, and you'll have to implement the string formatting (e.g. using fmt with a custom writer - you really don't want your asserts to allocate, in case you're asserting because of a failure in the allocator or an out-of-memory condition!) and the logging/dialog code, but that's the jist of what the assert macro looks like in so very many codebases I've worked on.

You can even extend the above to count how many times a particular assert has fired, whether to continue logging the assert while keeping just the dialog optionally disabled, and so on. There's a diminishing return on fanciness, but a key behavior is to allow people to ignore noisy/spammy assert failures. e.g. you might have an assertion failure on something important but non-game-breaking that would disable your QA team's ability to regress other bugs if the assert couldn't be disabled/ignored for the reason of the sesssion.

I've also seen versions that allow the developer to pass in a bool to the macro, allowing the assert to be disabled for individual pieces of content and not just for the file/line. You can also do all the disable checking internally (on a combination of message hash, file, line, etc.) though that will typically result in slow assertions even when disabled.

The dialogs will often be able to print the offending file/line, failed condition, optional message, and a callstack (which must be gathered in a platform-specific way), as well as provide options like [C]ontinue, reak, gnore Once, Ignore All, [D]isable All. The other trick is to make sure the dialogs are super easy to copy-and-paste, since the default dialogs on some platforms are missing the ability to copy text.

Share on other sites

Thanks for your help! This seems to do exactly what I want:

#define stopAll(errorString) \
MessageBoxA(NULL, errorString, "ERROR!", MB_OK | MB_ICONEXCLAMATION); \
__debugbreak();

called like this

stopAll ("Error in createTown");

I get a nice message-popup (and the end-user do too if the error remains in release).

I'm taken directly to the code where I messed up so I can debug.

Edited by suliman