Old School Ways of Handling Status and Error Conditions

C++ provides a number of different ways for returning status and error information from functions. C++17 added a number of new ones or modifications on previously defined ones. This post will look at how you may have done this 15 or more years ago. A second post will continue to this look into handling statuses and error conditions, including the techniques added in C++11, C++14, and C++17.

Let’s say we have a function that takes an input parameter and returns both an output value and a status or error indication. Here are a number of ways of doing this.

Status or Error Code as Function Parameter

Look at the following code:

void foo(int inputParam, int* outputParam, int* status)
{ /* do something */ }

int main()
{
    int in = 1;
    int out;
    int status;
    foo(in, &out, &status);
    if(status == OK) {
        /* use out */
    }
}

In the call to foo, out is valid only if status contains the value OK.

This code is obviously old school. Today, you would more likely use references rather than pointers for the parameters:

void foo(int inputParam, int& outputParam, int& status);

Status or Error Code As Return Value

In the code above, status is returned as a parameter in a subroutine call. Alternatively, and more frequently, the status or error code is set as the return value to a function. For example, foo might be redeclared as:

int foo(int inputParam, int& outputParam);

and used as follows:

int main()
{
    int in = 1;
    int out;
    int status = foo(in, out);
    if (status == OK) {
        /* do something with out value */
    }
}

Special Output Value

If there is a single value that indicates that an operation has failed, but this failure is not necessarily a fatal error condition, a special value may be returned in the output parameter. For example:

int foo(int inputValue)
{
    /* do something, returning valid value, or BadStatus if error condition */
}

int main()
{
    int out = foo(1);
    if ( out != BadStatus ) {
        /* do something with out */
    }
}

This occurs quite frequently, especially in functions that originated in C. A value of NULL might be used as a HANDLE value returned from the Windows libraries to indicate an error occurred. For example, in:

FILE* fp;
fp = fopen("myfile.txt", "r");

fp is NULL if the file myfile.txt does not exist or otherwise cannot be opened for reading.

Similar indicators are used in the Standard Templates Library. For example:

int val {4};
std::vector values {1, 2, 3};
auto iter = std::find(values.begin(), values.end(), val);
if(iter != values.end()) {
    /* iter contains iterator to value found in values vector */
}

In the code above, std::find returns values.end() because 4 is not a value in the values vector.

Return Status and Value in Struct

If a function returns more than one value, a struct containing these multiple values may be defined to contain each of the values. This may be extended to include a status or error code. For example:

struct Ret {
    int status;
    string returnedString;
public:
    Ret() : status(OK) {}
}

Ret foo(int input)
{
    Ret ret{};
    /* do something. Set returnedString if no error.
     * Otherwise, set status to indicate error
     */
    return ret;
}

int main()
{
    Ret returnValue = foo(6);
    if (returnValue.status == OK) {
        // do something with returnValue.returnedString
    }
}

Exceptions

Exceptions may be used to specify that something unexpected has occurred in the execution of the code and that normal processing should not continue. A thrown exception may indicate that the program cannot continue and should therefore be terminated, or that some special processing should be done before continuing.

As an example, let’s say you are creating a C++ library that encapsulates the Vulkan API. You have a Swapchain class that has a method called AcquireNextImage. Within this method, you call vkAcquireNextImageKHR that returns VK_SUCCESS if the call succeeded, VK_ERROR_OUT_OF_DATE_KHR if the drawing surface has changed in some way such as being resized, or any of a number of error statuses. For any of the error statuses, you probably want to inform the program’s user and then terminate the program. For VK_ERROR_OUT_OF_DATE_KHR, you will want to recreate the swap chain to match the new surface properties and then call AcquireNextImage again.

Throwing an exception for any of the error conditions is typical handling, but do you return a status code for the VK_ERROR_OUT_OF_DATE_KHR condition, or do you throw an exception indicating this status? Either would be acceptable, but since this condition happens rarely, I would throw an exception and handle it in the calling code. I think this is slightly cleaner than checking the return status from Swapchain::AcquireNextImage for multiple status values and processing each differently.

That is a rather long-winded description showing how to handle various status and error conditions using exceptions. Here is a simpler case:

void foo()
{
    /* do something */
    throw runtime_error("Error in foo");  // some error occurred
}

int main()
{
    try {
        foo();
    }
    catch (runtime_error& re) {
        /* handle the exception, then either continue after, or terminate */
    }
    // continue processing if non-fatal error occurred
    ...
}

 Review

This post looked into ways that you might return statuses and error codes from a subroutine or function. It also presented exceptions as an alternative. Each of these methods was available prior to C++11 and will certainly show up in legacy code.

The next post will review some of the additional methods that might be used as a result of functionality that was added in C++11, C++14, and C++17.

C++ Exceptions and wxWidgets

I have been working on a project to port a set of Vulkan tutorials from using GLFW as the windowing system to using wxWidgets. The code in the original tutorials throws std::runtime exceptions whenever there are unrecoverable Vulkan errors. These exceptions are caught in the program’s main function where the exception’s message is sent to cerr and the program is immediately terminated by returning a value from main. wxWidgets doesn’t work that way!

Here is the code from the first tutorial (original with GLFW, not wxWidgets):

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::runtime_error& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

There are two problems with this approach when using wxWidgets:

  1. wxWidgets uses the platform’s native API so on Windows systems, that means that wxWidgets applications are Win32 GUI applications. Win32 GUI applications do not by default have a console, so, again by default, outputting to cerr does nothing.
  2. wxWidgets hides the main function and much of the program and GUI initialization. Instead, wxWidgets provides wxApp::OnInit, a method that should be overridden to perform initialization including creating a top-level main window. wxApp::OnExit may be overridden to delete any wxWidgets objects that are not tied to top-level windows. Following the return from OnInit, wxWidgets the calls wxApp::OnRun to enter the main loop. OnRun is not normally overridden.

So, what do we do? It is possible to create a console from a Win32 application and to tie cerr to output to the console. This is not typical of Win32 applications, so it will not be discussed further. A better approach for unrecoverable Vulkan and other exceptions is to display a message box containing exception information and then to terminate the application. Additionally, information could be output to a logging file if desired.

wxWidgets provides an overview document for using exceptions with wxWidgets. The document recommends handling exceptions in the following locations in order of preference:

  1. In a try/catch block inside an event handler.
  2. In wxApp::OnExceptionInMainLoop().
  3. In wxApp::OnUnhandledException().

In my port of the tutorials, a large amount of Vulkan initialization occurs in OnInit, so it will be necessary to handle exceptions there, inside a try/catch block. The rest of the Vulkan code is called from the wxEVT_PAINT handler (OnPaint in my case).

Here is my code from OnInit:

bool VulkanTutorialApp::OnInit()
{
    VulkanWindow* mainFrame;
    try {
        mainFrame = new VulkanWindow(nullptr, wxID_ANY, L"VulkanApp");
    } 
    catch (const std::runtime_error& err) {
        // process runtime_error exception
        // terminate application
        ...
    }
    mainFrame->Show(true);
    return true;
}

Even though wxWidgets has not yet called wxApp::OnRun, a message box can still be displayed and processed before the application terminates.

And here is a bit of code from the Vulkan canvas’s OnPaint method:

void VulkanCanvas::OnPaint(wxPaintEvent& event)
{
    try {
        // paint the image on the canvas using Vulkan
        ...
    } catch(const std::runtime_error& err) {
        // process runtime_error exception
        /// then terminate program
        ...
    }

How do we display a message box and possibly log information to a file? wxWidgets provides a set of wxLog* functions. See the logging overview for information on these functions. The default action for wxLogError is to display a message box with the error information. wxLogFatalError calls wxLogError then calls abort. Since we want to terminate the application after displaying the error information, wxLogFatalError would at first glance appear to be a good choice. Alternatively, we could call wxMessageBox to display the error information and then terminate the application ourselves.

Using wxLogFatalError in OnInit is overkill because simply returning false will result in wxWidgets cleaning up and terminating the application. Specifically, the destructors for all created objects are called and the memory associated with the objects is released.

Here is my code for OnInit:

bool wxVulkanTutorialApp::OnInit()
{
    VulkanWindow* mainFrame;
    try {
        mainFrame = new VulkanWindow(nullptr, wxID_ANY, L"VulkanApp");
    } 
    catch (std::runtime_error& err) {
        wxMessageBox(err.what(), "Vulkan Error");
        return false;
    }
    mainFrame->Show(true);
    return true;
}

wxLogFatalError may still be useful in the OnPaint method, but keep in mind that it calls abort. abort immediately terminates the program without necessarily calling all object destructors and freeing any associated memory. On program termination, Microsoft Windows will free any memory associated with the application, and also release any OS resources (file handles, window handles, etc.) associated with the program. But the operating system does not know about any Vulkan resources and handles that the application may have obtained and therefore, these are not released; they will stay around until the computer is restarted.

So wxLogFatalError does not appear to be a good choice in OnPaint either. This leaves us with wxLogError or wxMessageBox calls and some method to terminate the application. As mentioned above, messages sent to wxLogError display the messages in a message box by default; however, the messages may be redirected elsewhere, such as to a log file. Because I want to always display the messages on screen, I have chosen to use wxMessageBox. wxApp::ExitMainLoop() tells wxWidgets to exit the main loop, which eventually terminates the application cleanly. Let’s try that; the catch block in OnPaint will look like this:

    catch(const std::runtime_error& err) {
        wxMessageBox(err.what(), "Vulkan Error");
        wxTheApp->ExitMainLoop()
    }

To test this, I modified OnPaint to throw a runtime_error. Hmm, no message box is displayed, the program does not terminate, and the program becomes unresponsive. A bit of time using the debugger shows that as soon as the wxMessageBox call is reached, wxWidgets returns control to the message loop which again calls OnPaint, which calls wxMessageBox, which returns control to the message loop which again calls OnPaint… Well, you get the idea.

We must move the calls to wxMessageBox and wxApp::ExitMainLoop outside of the OnPaint method. Prior to wxWidgets 2.9.x, this could be done by creating a new event type, and defining a handler to process that event. In OnPaint, we would queue the event to be processed. wxWidgets 3.x provides the CallAfter method to simplify this. Let’s create a new method called OnPaintException to display the error information and terminate the program:

void VulkanCanvas::OnPaintException(const std::string& msg)
{
    wxMessageBox(msg, "Vulkan Error");
    wxTheApp->ExitMainLoop();
}

Now the catch block in OnPaint would look like this:

    catch (const std::exception& err) {
        CallAfter(&VulkanCanvas::OnPaintException, err.what());
    }

CallAfter adds an event to be processed once OnPaint returns. Only then is OnPaintException called. The call to ExitMainLoop causes the application to terminate normally.

Rerunning our test with OnPaint generating a runtime_error exception, the message box is displayed and the program then terminates normally.