Boost logo

Boost :

Subject: [boost] [New Feature Suggestion] Dynamic exception handlers registration
From: Alex Tkachenko (alek.tkachenko_at_[hidden])
Date: 2009-02-18 09:27:23


Hello there.
A while back I wrote usefull tool for organizing exception handling. The
general idea is borrowed from boost::execution_monitor
(http://www.boost.org/doc/libs/1_38_0/libs/test/doc/html/execution-monitor.html)
. Similarly, I call this unit "Exception Monitor". It allows to dynamically
register exception converters and convert exception objects from any source
types to arbitrary target type.
For instance, if you write some integration code which uses several external
modules, and each of these modules could generate their own tree of exceptions,
you would face serious issue trying to implement error diagnostic.
One approach is to handle only base exception for each module and to catch them
in function main(). Let's assume that all exceptions are inherited from
std::exception, so we need to catch single base (std::exception). This approach
is convenient but not precise. Even if we want just to output error message
using std::exception::what() we have only some general low level error
description, moreover this description could be completely wrong. It doesn't
reflect actual cause of the error because at the place where the exception is
originally generated it couldn't know about context of execution.
On the contrary, we can catch low-level exceptions in the place where execution
context is more obvious and convert it to some "application" exception which
contains more information about current situation. This is the solution for
detailed error diagnostic, but it is too tedious. Also such code is unscalable,
error prone and hard to support. Look at the following example:
///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
class app_error: public virtual std::exception { ... };

void foo() {
    try {
        ...local state...
        unit1::foo(...);
        unit2::foo(...);
    }
    catch(const unit1::exception1& ex) {
        //...convert caught exception to app_error using local state
        throw app_error(...);
    }
    catch(const unit1::exception2& ex) {
        //...convert caught exception to app_error using local state
        throw app_error(...);
    }
    catch(const unit2::exception& ex) {
        //...convert caught exception to app_error using local state
        throw app_error(...);
    }
}

int main() {
    try {
        foo();
    }
    catch(const app_error& ex) {
        cout << "Error: " << ex.what() << endl;
    }
    catch(const std::exception& ex) {
        cout << "Unknown error: " << ex.what() << endl;
    }
    catch(...) {
        cout << "Unknown error" << endl;
    }
}

The main drawback of this source code is that exception handling is mixed with
'main' code. It would be better if we could write something like this:
///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
class app_error: public virtual std::exception { ... };

typedef exc::catcher<app_error> app_catcher; //...exception catcher type
typedef std::list<app_catcher> app_catchers; //...container for catchers

struct convert_unit1_ex1 {
    const app_error operator()(const unit1::exception1&);
};
struct convert_unit1_ex2 {
    const app_error operator()(const unit1::exception2&);
};
struct convert_unit2_ex {
    const app_error operator()(const unit2::exception&);
};

app_catchers excon;

void foo() {
    ...local state...
    //...register catchers for possible exceptions
    excon.push_front( app_catcher::create<unit1::exception1>(
        convert_unit1_ex1(...local state...)));
    excon.push_front( app_catcher::create<unit1::exception2>(
        convert_unit1_ex2(...local state...)));
    excon.push_front( app_catcher::create<unit2::exception>(
        convert_unit2_ex(...local state...)));
    //...
    unit1::foo();
    unit2::foo();
    //...good idea is to use RAII to register catchers to avoid this
    //...manual deregistration:
    excon.pop_front();
    excon.pop_front();
    excon.pop_front();
}

int main() {
    try {
        guard(excon, foo());
    }
    catch(const app_error& ex) {
        cout << "Error: " << ex.what() << endl;
    }
    catch(const std::exception& ex) {
        cout << "Unknown error: " << ex.what() << endl;
    }
    catch(...) {
        cout << "Unknown error" << endl;
    }
}

Pluses of this technique:
1) it encourages developer to implement exception handlers code separatly
from 'main' code;
2) it provides framework for detailed exception handling;
3) it offers flexible means for structural arrangement of exception handlers'
source code.

Very interesting feature of Exception Monitor is ability to sort catchers in
"proper" order regarding their inheritance. So catcher for std::runtime_error
should be earlier then std::exception. This is what functions exc::insert and
exc::replace do.
Library source code (it's tested it on gcc 4.3.1):

#ifndef EXCEPTION_MONITOR_HEADER_GUARD
#define EXCEPTION_MONITOR_HEADER_GUARD
/*
 * Exception Monitor:
 * allowes dynamic exception handlers registration
 * Author: Alex Tkachenko
 * 2009.02.10
*/

#include <cassert>
#include <boost/function.hpp>
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_base_of.hpp>
#include <boost/shared_ptr.hpp>
#include <typeinfo>

namespace exc
{
namespace details
{
class failed_to_catch_exception {};
//...
class try_catcher_base
{
public:
    virtual ~try_catcher_base() {}
    virtual void convert() const = 0;
    virtual void try_throw_self_exception() const = 0;
    virtual bool try_catch_self_exception(
        const try_catcher_base& other) const = 0;
    virtual const std::type_info& type() const = 0;
};

template<typename Texception_to, typename Texception_from>
class try_catcher: public try_catcher_base
{
    typedef boost::function<const Texception_to (const Texception_from&)>
        converter_f;
public:
    try_catcher(const converter_f& iconverter):
        converter(iconverter)
    {
        assert(!converter.empty() && "Null converter");
    }
private:
    void convert() const
    {
        try
        {
            throw;
        }
        catch(const Texception_from& ex)
        {
            throw converter(ex);
        }
        catch(...)
        {
            throw failed_to_catch_exception();
        }
    }
    void try_throw_self_exception() const
    {
        throw (Texception_from*)0;
    }
    bool try_catch_self_exception(const try_catcher_base& other) const
    {
        try
        {
            other.try_throw_self_exception();
        }
        catch(Texception_from*)
        {
            return true;
        }
        catch(...)
        {
            return false;
        }
    }
    const std::type_info& type() const
    {
        return typeid(Texception_from);
    }
    //...
    converter_f converter;
};
}//...namespace details

template<typename Texception_to>
class catcher
{
    typedef boost::shared_ptr<details::try_catcher_base> impl_t;
    catcher(const impl_t& iimpl): impl(iimpl)
    {
        assert(impl && "NULL catcher implementation");
    }
    void convert() const
    {
        impl->convert();
    }
    void try_throw_self_exception() const
    {
        impl->try_throw_self_exception();
    }
    bool try_catch_self_exception(const catcher& other) const
    {
        return impl->try_catch_self_exception(*other.impl);
    }
public:
    typedef Texception_to Texception_to_type;
    template<typename Texception_from>
    static const catcher create(
        const boost::function<const Texception_to (const Texception_from&)>
            converter)
    {
        //...it's senseless to convert from derived to base exception,
        //...or convert exception to its own type
        BOOST_STATIC_ASSERT(
            (!boost::is_base_of<Texception_to, Texception_from>::value));
        return catcher(
            impl_t(
                new details::try_catcher<Texception_to, Texception_from>(
                    converter)));
    }
    //...
    const std::type_info& type() const
    {
        return impl->type();
    }
    void swap(catcher& rhs)
    {
        impl.swap(rhs.impl);
    }
private:
    template<typename Treturn, typename Tcatchers>
    friend Treturn guard(
        const Tcatchers& catchers,
        const boost::function<Treturn ()>& func);
    template<typename TcatchersIterator>
    friend TcatchersIterator find(
        TcatchersIterator begin,
        TcatchersIterator end,
        const typename std::iterator_traits<TcatchersIterator>::value_type&
            catcher);
    //...
    impl_t impl;
};

template<typename Treturn, typename Tcatchers>
Treturn guard(
    const Tcatchers& catchers, const boost::function<Treturn ()>& func)
{
    assert(!func.empty() && "Empty function");
    typedef typename Tcatchers::value_type::Texception_to_type
        Texception_to_type;
    try
    {
        return func();
    }
    catch(...)
    {
        const typename Tcatchers::const_iterator end(catchers.end());
        for(typename Tcatchers::const_iterator iter(catchers.begin());
            end != iter;
            ++iter)
        {
            try
            {
                iter->convert();
            }
            catch(const Texception_to_type&)
            {
                throw;
            }
            catch(details::failed_to_catch_exception)
            {
                //...try next handler
            }
        }
        throw;
    }
}

template<typename Tcatchers>
inline void guard(
    const Tcatchers& catchers, const boost::function<void ()>& func)
{
    guard<void, Tcatchers>(catchers, func);
}

template<typename Texception_to>
inline bool operator==(
    const catcher<Texception_to>& lhs, const catcher<Texception_to>& rhs)
{
    return lhs.type() == rhs.type();
}

template<typename Texception_to>
inline bool operator!=(
    const catcher<Texception_to>& lhs, const catcher<Texception_to>& rhs)
{
    return !(lhs == rhs);
}

template<typename Texception_to>
inline void swap(catcher<Texception_to>& lhs, catcher<Texception_to>& rhs)
{
    lhs.swap(rhs);
}

template<typename TcatchersIterator>
TcatchersIterator find(
    TcatchersIterator begin,
    TcatchersIterator end,
    const typename std::iterator_traits<TcatchersIterator>::value_type& catcher)
{
    for(;end != begin; ++begin)
        if(begin->try_catch_self_exception(catcher))
            return begin;
    return end;
}

template<typename Tcatchers>
void insert(Tcatchers& catchers, const typename Tcatchers::value_type& catcher)
{
    typename Tcatchers::iterator iter(
        exc::find(catchers.begin(), catchers.end(), catcher));
    catchers.insert(iter, catcher);
}

template<typename Tcatchers>
void replace(Tcatchers& catchers, const typename Tcatchers::value_type& catcher)
{
    typename Tcatchers::iterator iter(
        exc::find(catchers.begin(), catchers.end(), catcher));
    if(catchers.end() == iter)
        catchers.push_back(catcher);
    else if(catcher == *iter)
        *iter = catcher;
    else
        catchers.insert(iter, catcher);
}

} //...namespace exc

#endif /*EXCEPTION_MONITOR_HEADER_GUARD*/


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk