Friday, December 13, 2013

Using a Lippincott Function for Centralized Exception Handling

The lippincott function is a way to wrap the handling of many different exceptions into a single reusable function.

Consider this use case:
  • A C++ library (say, libfoo) has many distinct exception types.
  • You want to wrap the C++ library with a C API.
  • You want error codes instead of exceptions for the C API.

A simple approach is to write every C API function as follows:
typedef enum foo_Result {
    FOO_OK,
    FOO_ERROR1,
    FOO_ERROR2,
    FOO_UNKNOWN
} foo_Result;

foo_Result foo_dothing()
{
    try
    {
        // Can throw MyException1 or MyException2
        foo::DoThing();
    }
    catch (const MyException1&)
    {
        return FOO_ERROR1;
    }
    catch (const MyException2&)
    {
        return FOO_ERROR2;
    }
    catch (...)
    {
        return FOO_UNKNOWN;
    }

    return FOO_OK;
}
There are some maintenance problems with the above approach:
  • DoThing() might later throw more exception types.
  • You must repeat the exception handling code for every API function.

Jon Kalb suggests the following refactoring method, which he named after Lisa Lippincott, who taught him the technique.
foo_Result lippincott()
{
    try
    {
        throw;
    }
    catch (const MyException1&)
    {
        return FOO_ERROR1;
    }
    catch (const MyException2&)
    {
        return FOO_ERROR2;
    }
    catch (...)
    {
        return FOO_UNKNOWN;
    }
}

foo_Result foo_dothing()
{
    try
    {
        foo::DoThing();
        return FOO_OK;
    }
    catch (...)
    {
        return lippincott();
    }
}
"throw;", when inside a catch block, simply rethrows the currently caught exception. In this case, the "throw;" is not directly placed in a catch block within the lippincott() function, but it is transitively (safely) called from within the catch block of foo_dothing().

There are some important preconditions to calling lippincott():
  • You cannot call the lippincott function from outside a catch block.
    • "throw;" outside of a catch block will call std::terminate().
  •  lippincott() must be noexcept. No exceptions should escape it.
    • The exception will leak out of the C API otherwise.

If we want extra safety, we can implement safeguards for the aforementioned preconditions:

To safely handle a violation of the first precondition, we can check that std::current_exception()  is not null previous to doing the "throw;"

To prevent an exception from being thrown out of lippincott(), we can wrap the whole body of the function in a try/catch.

Here is the "extra safe"/paranoid version of lippincott():
foo_Result lippincott()
try
{
    try
    {
        if (std::exception_ptr eptr = std::current_exception())
        {
            std::rethrow_exception(eptr);
        }
        else
        {
            return FOO_UNKNOWN;
        }
    }
    catch (const MyException1&)
    {
        return FOO_ERROR1;
    }
    catch (const MyException2&)
    {
        return FOO_ERROR2;
    }
    catch (...)
    {
        return FOO_UNKNOWN;
    }
}
catch (...)
{
    return FOO_UNKNOWN;
}
The C API can now be written entirely in the style of foo_dothing(), which will centralize the error code conversion through the lippincott function.

Another interesting idea is to use the lippincott function to convert exceptions into string representations for debugging. This area is especially in need of the extra try/catch surrounding the function, since allocating dynamic strings can fail.

Full working example: http://ideone.com/m2ZfHN

The term "lippincott function" was popularized by Jon Kalb in his exception-safe coding talks.
You can find his explanation here: youtube link

Wednesday, December 4, 2013

Correcting the transitivity of const in C++

C++ allows you to make an instance of a class const. This normally prevents making changes to any member variables and also prevents the user to call any member functions which are not also marked const.

There is, however, a quirk which you must be aware of. Consider the following code:
struct A {
    int* x;
    A(): x{new int} {}
   ~A() { delete x; }
};

int main()
{
    const A a;
    *a.x = 3;
}
This code compiles and runs with no problems, even though it might appear to be writing over a read-only piece of data.
The type of a.x reveals the reason why this is allowed:
int * const
This denotes a "Constant pointer to int", which is different from a "Pointer to a constant int" or a "Constant pointer to a constant int".
In this case of "Constant pointer to int", overwriting the pointer is not allowed, but overwriting the int it points to is possible.
This may be desired behaviour in some cases, but in others it is not.

This differs from the D Programming Language, where both const and immutable are transitive by default (although in a slightly different but cool way.)

One simple way to prevent the possibly undesired behaviour is to use access modifiers and getters to prevent direct access from the user:
struct A {
    A(): x_{new int} {}
   ~A() { delete x_; }

          int& x()       { return *x_; }
    const int& x() const { return *x_; }
private:
    int* x_;
};
Now it is impossible* to access a mutable reference to *x_ through a const A.
However, it is still possible to write to *x_ from within const member functions of A. This makes it possible for const member functions to have side-effects on the class which are unexpected by the user.
[*]: again, nothing is impossible in C++.

C++11's smart pointers also have the property of not being transitively const.
Note the signatures of these std::unique_ptr member functions:
pointer std::unique_ptr::get() const;

typename std::add_lvalue_reference<T>::type
         operator*() const;  

pointer operator->() const;
These methods all return non-const pointers and references, even if the method is called on a const std::unique_ptr instance.

We could keep enforcing the transitive const relationship by writing the const and non-const getters for every publicly exposed member, but there exists a more general way to solve this problem: We can write a smart pointer with built-in transitivity for const.

std::unique_ptr is not far from the mark, so let's work on top of it:
template<
    class T,
    class Deleter = std::default_delete<T>
> class transitive_ptr : public std::unique_ptr<T,Deleter>
{
public:
    // inherit typedefs for the sake of completeness
    typedef
        typename std::unique_ptr<T,Deleter>::pointer
        pointer;
    typedef
        typename std::unique_ptr<T,Deleter>::element_type
        element_type;
    typedef
        typename std::unique_ptr<T,Deleter>::deleter_type
        deleter_type;

    // extra typedef
    typedef
        const typename std::remove_pointer<pointer>::type*
        const_pointer;

    // inherit std::unique_ptr's constructors
    using std::unique_ptr<T,Deleter>::unique_ptr;
 
    // add transitively const version of get()
    pointer get() {
        return std::unique_ptr<T,Deleter>::get();
    }
    const_pointer get() const {
        return std::unique_ptr<T,Deleter>::get();
    }

    // add transitively const version of operator*()
    typename std::add_lvalue_reference<T>::type
    operator*() {
        return *get();
    }
    typename std::add_lvalue_reference<const T>::type
    operator*() const {
        return *get();
    }
 
    // add transitively const version of operator->()
    pointer operator->() {
        return get();
    }
    const_pointer operator->() const {
        return get();
    }
};
Attempting to write to a transitively const instance of a pointer will now fail, and our class declaration is also much more concise because we were able to convey the rules for using x within its type:
struct A {
    transitive_ptr<int> x;
    A(): x{new int} {}
};

int main() {
    const A a;
    *a.x = 3;
}
Compiler output:
error: assignment of read-only location 
‘a.A::x.transitive_ptr<T, Deleter>::operator*
<int, std::default_delete<int> >()’
*a.x = 3;
     ^
This is another useful smart pointer to add to our smart pointer tool box, getting us one step closer to always correctly enforcing the rule of zero.

Full working example: http://ideone.com/0RUr3V