NOTE: Every post ends with "END OF POST". If you don't see it then open the full post in a separate page!

Review: “Andrey Alexandrescu, Systematic Error Handling in C++” (Expected, ScopeGuard)

This is a review of a video lecture given by Andrey Alexandrescu on Systematic Error Handling in C++

Video:
* http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
Slides:
* http://sdrv.ms/RXjNPR or
* https://onedrive.live.com/view.aspx?resid=F1B8FF18A2AEC5C5!1158&app=WordPdf&authkey=!APo6bfP5sJ8EmH4

Andrey Alexandrescu introduces two nice C++ classes to manage and handle errors in C++.
* Expected<T>
* ScopeGuard

Expected<T> is a template class. It is a bit similar to std::optional<T> (or boost::optional<T>) but it also carries the reason for an invalid value in the form of an exception object. Internally, it is a union that has either a T object or an exception.

It is easiest to understand it through a use-case, taken from Slide 28:

Expected<int> parseInt(const std::string& s)
{
    int result;
    ...
    if (nonDigit)
    {
        return Expected<int>::fromException(std::invalid_argument("not a number"));
    }
    ...
    if (tooManyDigits)
    {
        return Expected<int>::fromException(std::out_of_range("overflow"));
    }
    ...
    return result;
}

In the example above, we either return a valid result or an invalid result represented by an exception object. Note, that we do not throw the exception here. We just return it.

The caller then can decide whether to handle the exception immediately or (re)throw it. Example from Slide 29:

// Caller
string s = readline();
auto x = parseInt(s).get(); // Throw on error
auto y = parseInt(s); // Won’t throw
if (!y.valid()) {
    // Handle locally
    if (y.hasException<std::invalid_argument>()) {
        // Not an int
    }
    y.get(); // Just "re"throw
}

ScopeGuard is a class that executes the provided callable object at scope exit (i.e. at function exit and when exceptions occur).
Andrey provides some convenience macros fur using ScopeGuard but I think it is easier to see what it does without macros. Here is a usage example from slide 42:

// Assuming that action1() and action2() does not need neither cleanup nor rollback if they fail.

action1(); // Throws if fails
auto cleanup1 = ScopeGuard{[&] { doCleanup1(); }};
auto rollback1 = ScopeGuard{[&] { doRollback1(); }};

action2(); // Throws if fails
auto cleanup2 = ScopeGuard{[&] { doCleanup2(); }};
auto rollback2 = ScopeGuard{[&] { doRollback2(); }};

nextAction(); // Throws if fails

rollback1.dismiss();
rollback2.dismiss();

// cleanup1 and cleanup2 called at scope exit.

Although you can abuse e.g. std::unique_ptr to do RAII with a fake pointer (a pointer that does not point to a valid address) and a custom deleter, the resulting code may be misleading because it has nothing to do with pointers (the use of unique_ptr, the mysterious 0xfefefefe, the unused void* in doCleanup()).

// This code is a bit misleading. We just want to clean up but we mess with an API meant for pointers.

void doCleanup(void *)
{
    ...
}

void Action()
{
    ...
    auto cleanup = std::unique_ptr<void, decltype(doCleanup)*>{
                       reinterpret_cast<void *>(0xfefefefe), doCleanup };
    ...
    // doCleanup() called at scope exit or upon exception.
}

ScopeGuard provides a clear API for using RAII with unsafe/legacy code.

END OF POST