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.

No comments:

Post a Comment