Showing posts with label error handling. Show all posts
Showing posts with label error handling. Show all posts

Saturday, 14 January 2012

Construction failure

--health;
employed = false;
yearsMarried = 5;

As mentioned in my previous articles about default construction, a common pattern that you see is the use of the default constructor in order to create a 'zombie' state, followed by a post-construction initialisation function which 'constructs' it properly. This is often to do with deficient frameworks which want to create your objects for you and, because they don't generally know the constructor parameter lists of classes they don't define, they rely on default construction to create the object and let the class author deal with proper initialisation at a later time.

However, it's not always the case. It's often the case that a post-construction initialisation step is provided in order to return an error code which specifies whether or not initialisation was successful. The argument is that constructors don't provide return values so how can you possibly know if it succeeded?

ErrorCode::type MyResource::Initialise()
{
    handle_ = AllocateResource();
    if (!handle_)
        return ErrorCode::allocation_failure;

    return ErrorCode::ok;
}

Of course, the real method of signalling errors from constructors is through the use of exceptions. However, quite often the penalty for reporting errors in this way is not one which the executing program can afford. I'm used to the games programming world where exceptions are typically disabled because the cost of enabling them is too high. RTTI (run-time type information) also needs to be enabled, which means that your program footprint is being filled with type metadata, exception tables and stack unwinding hooks, which can be considerably expensive in terms of memory consumption. Isn't it better to use this memory for something players will appreciate?

We still want our class invariants to hold. That is an important thing to maintain for the sake of code quality and reasoning about the program, so we want to avoid a second initialisation step if we can. We can't throw an exception from our constructor so what do we do?

One option is to pass out an error code out from the constructor via a reference:

MyResource::MyResource(ErrorCode::type& error)
{
    handle_ = AllocateResource();
    error = handle_ ? ErrorCode::ok : ErrorCode::allocation_failure;
}

This works, but it's of little use; you still have a 'zombie' state which the class needs to consider, even if it's as simple as having the class handle the case where the user decides to use your object after ignoring the error code. We've failed to maintain a strict invariant.

On the other hand, what does it mean for a class to fail initialisation during execution of a game, or any other realtime system? If you're driving around a city and a landmark pops into view which is unique to the area you've just driven into, and you can't allocate the resources for that landmark, how do you deal with that? You can't just pop up an error and hope that the player forgives you for the grievous interruption of their game. You can't just swallow the error and stop the landmark appearing - imagine if you were driving around San Francisco and you couldn't allocate the resources for the Golden Gate Bridge. There would just be a big hole in the world that you'd fall through.

Pre-allocation is where it's at. By pre-allocating all of your resources at the start of your program, you can do all of that error checking once, in one place, and then you can start dishing them out as the code requires them. Your class constructor simply requests one of these resources:

#define MAX_RESOURCES 256
std::vector<ResourceHandle> g_handles;

int main()
{
    for (int i = 0; i != MAX_RESOURCES; ++i)
    {
        ResourceHandle h = AllocateResource();
        if (!h)
            exit(1); // Your game is never going to run if we can't allocate these
        g_handles.push_back(h);
    }

    // Run game
}

MyResource::MyResource()
{
    // We're assuming we have handles available to us
    assert(!g_handles.empty());

    // Steal a handle from the global vector
    handle_ = g_handles.pop_back();
}

MyResource::~MyResource()
{
    // Put it back again when we're finished with it
    g_handles.push_back(handle_);
}

Now our constructor is guaranteed to run without failure. That's assuming that there are still some handles available, of course, but this will typically be the case where the resource limitations are known up-front and the game code is deliberately constructed in such a way as to not exceed those limitations. We're still taking advantage of RAII by ensuring that the resource is returned to the pool on destruction, and our invariant is strong because the constructor running to completion means that the object has acquired the resource.

Sunday, 1 January 2012

Parameterised error handlers

++year;

Last post I suggested using a default value in place of an exception to be returned when the function fails to return its proper result:

type from_ascii(const char* c, type resultOnFailure)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
            return (type)i;

    return resultOnFailure;
}

This is fine but its usage is limited to those occasions where you have an appropriate default in advance of calling the function. Also, sometimes the construction of the default value is non-trivial and isn't something you want to waste time doing (or worse, have the construction fail) if you don't even end up using it.

Enter parameterised error handling. The idea is to provide is a functor to be invoked when the return value cannot be generated:

type from_ascii(const char* c, std::function<type()> onError)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
            return (type)i;

    return onError();
}

Now we can do what we like to handle the error. We can throw an exception. We can return the default value we want. We can prompt the user for a value. We can read it from a file. We can log the error or inform the user before doing any of the above, especially if you forward the function arguments to the functor:

type from_ascii(const char* c, std::function<type(const char*)> onError)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
            return (type)i;

    return onError(c);
}

Calling this function is simply a matter of passing an appropriately-defined functor. C++11's lambda support makes this particularly easy:

auto e = Direction::from_ascii("banana", [](const char* c) {
    Log("Invalid Direction::type name: %s", c);
    return Up;
});

You can define your functor however you like, passing what you think is important, maybe an error code which tells you why the functor got invoked, or a flag which the functor can set to retry the operation.

Finally, if performance is a concern, you can always take the functor as a templated argument, then you can have your cake and eat it:

template <typename ErrorFunc>
type from_ascii(const char* c, ErrorFunc onError)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
            return (type)i;

    return onError(c);
}

This approach isn't appropriate for everything but it's another feather to your error handling cap.

Saturday, 31 December 2011

Error defaults

The other day, I wrote a function which threw an exception when it failed to convert an ASCII string into an enumerator:

type from_ascii(const char* c)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
            return (type)i;

    throw conversion_error_exception();
}

Arguably, the use of an exception here is slightly gratuitous. It's how C# does things, and it's not wrong, but it's not usually the best API when we come to use it. And of course there are performance concerns.

You usually see two solutions to this kind of problem, none of which are particularly appealing to me.

First is the 'sentinel value' solution:

enum type
{
    Left,
    Right,
    Up,
    Down,

    Unknown
};

type from_ascii(const char* c)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
            return (type)i;

    return Unknown;
}

The Unknown state is somewhat untidy, though is fine for enums with limited scope within a program, where the author of the enum is likely to be the only one using it and he knows all the contexts in which an Unknown enumerator needs to be handled. Additionally, there is likely to be no overhead, assuming that the addition of the extra state doesn't require the enum's underlying type to change.

The other popular solution is the pass-by-reference method:

bool from_ascii(const char* c, type& result)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
        {
            result = (type)i;
            return true;
        }

    return false;
}

Oddly enough, C# also supports this method. However, it's a bad method as you have lost referential transparency and strict value semantics.

Here is another solution. Quite often, I find myself just defaulting an enum variable to a particular state if the conversion fails. So why not just make that part of the function?

type from_ascii(const char* c, type resultOnFailure)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
            return (type)i;

    return resultOnFailure;
}

This doesn't solve every problem though. Another technique coming soon.