динамическая обработка исключений
От: s.p.  
Дата: 07.11.05 12:39
Оценка:
Иногда (например при состыковке библиотек) возникает подобный код:
try{
  DoSomething();
}
catch(std::exception &){
  throw MyError();
}
catch(DB::Exception& de){
  throw MyError();
}
...
catch(...){
  throw MyError();
}

ИМХО это громоздко и плохо расширяемо.
Также иногда хотелось бы в библиотеке позволить пользователю выбирать некритические исключения, обрабатываемые "на месте", задаваемые пользователем же обработчиками.
Предлагаю следующий вариант решения таких вопросов
/// генератор кортежей из последовательности типов
template< typename Types > 
struct TupleGen 
    : boost::mpl::reverse_fold<
        Types
        , boost::tuples::null_type
        , boost::tuples::cons<boost::mpl::_2,boost::mpl::_1>
    >
{
};
class AnyException
{
};
/// обработчик исключений
template<
typename ETL,
typename HTL,
typename BT=void(*)()
>
class ExcHandler
{
public:
    typedef BT BodyType;
    typedef HTL HandlersTypeList;
    typedef ETL ExceptionTypeList;
    typedef typename TupleGen<HandlersTypeList>::type HandlersType;
    ExcHandler(const BodyType &abody, const HandlersType &hd)
        :body_(abody), handlers_(hd){            
//            Если вываливаемся тут, значит кол-во типов в массиве
//            ExceptionTypeList не равно кол-ву типов в массиве HandlersTypeList.
            BOOST_MPL_ASSERT_MSG(boost::mpl::size<HandlersTypeList>::type::value==
                boost::mpl::size<ExceptionTypeList>::type::value, 
                HANDLERS_EXCEPIONS_ARRAY_MISTMATCH, (int));
    }
    void operator()(){
        typedef typename boost::mpl::int_<boost::mpl::size<ExceptionTypeList>::value-1> TP;
        this->Exec(TP());
    }
private:
    BodyType body_;
    HandlersType handlers_;
    template<typename T> void Exec(T){
        typedef typename boost::mpl::at_c<ExceptionTypeList, T::value>::type CurrentExType;
        TryCatch<T, CurrentExType>(boost::is_same<CurrentExType, AnyException>());
    };

    void Exec(boost::mpl::int_<-1>::type){
        body_();
    };

    template<typename T, typename Ex> void TryCatch(boost::false_type){
        try{
            Exec(typename boost::mpl::prior<T>::type());
        }
        catch(Ex &ex){
            boost::get<T::value>(handlers_)(ex);
        }
    }
    
    template<typename T, typename Ex> void TryCatch(boost::true_type){
        try{
            Exec(typename boost::mpl::prior<T>::type());
        }
        catch(...){
            AnyException e;
            boost::get<T::value>(handlers_)(e);
        }
    }

Пример использования:

using namespace boost::mpl;
using namespace boost;
// класс исключений
class MyException{
};
// тело try-блока - должно иметь operator()()
void gg()
{
    cout<<"insight try|catch"<<endl;
    throw runtime_error("sdasd");
}
// обработчики исключений
void RE(runtime_error& e){cout<<"r_t handler:"<<e.what()<<endl;}
void E(exception& e){cout<<"e handler:"<<e.what()<<endl;}
void ME(MyException&){cout<<"m_e handler"<<endl;}
void Unk(AnyException &){cout<<"unknown error";}
///использование
void g(){
    // типы перехватываемых исключений, AnyException - для catch(...)
    typedef boost::mpl::vector
    <
        runtime_error, exception, MyException, AnyException
    > exc;
    // типы обработчиков исключений - должны иметь operator()(Exception&), 
    // Exception - перехватываемое исключение
    typedef boost::mpl::vector
    <
          void(*)(runtime_error&)
        , void(*)(exception&)
        , void(*)(MyException&)
        , void(*)(AnyException&)
    > hand;
    // сам обработчик, в качестве параметров передаем тело try-блока и набор обработчиков
    ExcHandler<exc, hand> hd(&gg, make_tuple(&RE, &E, &ME, &Unk));    
    hd();
}

Или вот так:
class Handler{
public:
    void operator()(runtime_error& e){cout<<"r_t handler:"<<e.what()<<endl;}
    void operator()(exception& e){cout<<"e handler:"<<e.what()<<endl;}
    void operator()(MyException&){cout<<"m_e handler"<<endl;}
    void operator()(AnyException &){cout<<"unknown error";}
    void operator()(){
        cout<<"insight try|catch"<<endl;
        throw MyException();
    }
};
....
typedef boost::mpl::vector
<
    runtime_error, exception, MyException, AnyException
> exc;
typedef boost::mpl::vector
    <
    Handler,
    Handler,
    Handler,
    Handler
    > hand;
Handler h;
ExcHandler<exc, hand, Handler>  hd(h, make_tuple(h, h, h, h));    
hd();

Поведение отличается от стандартного в случае если при обработке исключения выбрасывается исключение типа обрабатываемого ниже, то оно будет обработано ниже(т.е. если в предыдущем примере в обработчике runtime_error выкинуть например logic_error, то последне будет перехвачено обработчиком exception)
Проверялось на MSVC 7.1, gcc 4.0.0, boost 1.33.0.
Жду критики
Re: динамическая обработка исключений
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 07.11.05 14:16
Оценка: 15 (4) +9 :))) :)
Здравствуйте, s.p., Вы писали:

SP>Иногда (например при состыковке библиотек) возникает подобный код:

SP>
SP>try{
SP>  DoSomething();
SP>}
SP>catch(std::exception &){
SP>  throw MyError();
SP>}
SP>catch(DB::Exception& de){
SP>  throw MyError();
SP>}
SP>...
SP>catch(...){
SP>  throw MyError();
SP>}
SP>

SP>ИМХО это громоздко и плохо расширяемо.

Не, как раз это было просто и понятно. А вот то что было дальше, даже с двух стаканов не понять

Посмотри здесь
Автор: 0xDEADBEEF
Дата: 05.10.05
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re: динамическая обработка исключений
От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
Дата: 08.11.05 02:26
Оценка:
Здравствуйте, s.p., Вы писали:

А динамика где?

И чем такая реализация лучше, например, такого кода:

//
// Эти функции могут прикрывть афигенно сложный слой, управляющий обработкой ошибок.
// Возвращают false, если не удалось обработать входной параметр.
//
bool handle_std_exception(const std::exception &);
bool handle_db_exception(const DB::Exception &);
bool handle_unknown_exception();

try{
  DoSomething();
}
catch(std::exception &e){
  if (!handle_std_exception(e)) throw;
}
catch(DB::Exception& de){
  if (!handle_db_exception(de)) throw;
}
...
catch(...){
  if (!handle_unknown_exception()) throw;
}


?

Блоки catch можно макросами заменить, а управление "локальностью" обработки можно сделать, например, так:

typedef void (*std_exception_handler_type) (const std::exception &);
typedef void (*DB_exception_handler_type) (const DB::Exception &);

std_exception_handler_type push_handler(std_exception_handler_type);
void pop_handler(std_exception_handler_type);

DB_exception_handler_type push_handler(DB_exception_handler_type);
void pop_handler(DB_exception_handler_type);

// Устанавливает заданный обработчик при создании и удаляет при разрушении.
template<typename Handler>
class SetHandler
{
    public:
        SetHandler(Handler h) : h_(h)
        {
          h_ = push_handler(h);
        }
        ~SetHandler()
        {
          pop_handler(h_);
        }
        
    Handler h_;
};

//...

// Настройка обработчика: пока не разрушен sh, std::exception будут передаваться в my_new_handler.
SetHandler<std_exception_handler_type> sh(my_new_handler);

// Вызов
gg();
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Re[2]: динамическая обработка исключений
От: pf79 Украина  
Дата: 08.11.05 07:40
Оценка:
Здравствуйте, Коваленко Дмитрий, Вы писали:

КД>Не, как раз это было просто и понятно. А вот то что было дальше, даже с двух стаканов не понять


Что именно — реализацию или использование ??

КД>Посмотри здесь
Автор: 0xDEADBEEF
Дата: 05.10.05


Интересное решение, жаль сам не нашел — сэкономил бы несколько часов времени.
Re[2]: динамическая обработка исключений
От: s.p.  
Дата: 08.11.05 07:53
Оценка:
Здравствуйте, Геннадий Васильев, Вы писали:

ГВ>А динамика где?


динамика — выбор перехватываемых исключений равно как и обработчиков в рантайме.

ГВ>И чем такая реализация лучше, например, такого кода:


ГВ>
ГВ>//
ГВ>// Эти функции могут прикрывть афигенно сложный слой, управляющий обработкой ошибок.
ГВ>// Возвращают false, если не удалось обработать входной параметр.
ГВ>//
ГВ>bool handle_std_exception(const std::exception &);
ГВ>bool handle_db_exception(const DB::Exception &);
ГВ>bool handle_unknown_exception();

ГВ>try{
ГВ>  DoSomething();
ГВ>}
ГВ>catch(std::exception &e){
ГВ>  if (!handle_std_exception(e)) throw;
ГВ>}
ГВ>catch(DB::Exception& de){
ГВ>  if (!handle_db_exception(de)) throw;
ГВ>}
ГВ>...
ГВ>catch(...){
ГВ>  if (!handle_unknown_exception()) throw;
ГВ>}
ГВ>


ГВ>?


Тем что в рантайме я могу изменить список перехватываемых исключений.

ГВ>Блоки catch можно макросами заменить, а управление "локальностью" обработки можно сделать, например, так:


ГВ>
ГВ>typedef void (*std_exception_handler_type) (const std::exception &);
ГВ>typedef void (*DB_exception_handler_type) (const DB::Exception &);

ГВ>std_exception_handler_type push_handler(std_exception_handler_type);
ГВ>void pop_handler(std_exception_handler_type);

ГВ>DB_exception_handler_type push_handler(DB_exception_handler_type);
ГВ>void pop_handler(DB_exception_handler_type);

ГВ>// Устанавливает заданный обработчик при создании и удаляет при разрушении.
ГВ>template<typename Handler>
ГВ>class SetHandler
ГВ>{
ГВ>    public:
ГВ>        SetHandler(Handler h) : h_(h)
ГВ>        {
ГВ>          h_ = push_handler(h);
ГВ>        }
ГВ>        ~SetHandler()
ГВ>        {
ГВ>          pop_handler(h_);
ГВ>        }
        
ГВ>    Handler h_;
ГВ>};

ГВ>//...

ГВ>// Настройка обработчика: пока не разрушен sh, std::exception будут передаваться в my_new_handler.
ГВ>SetHandler<std_exception_handler_type> sh(my_new_handler);

ГВ>// Вызов
ГВ>gg();

ГВ>

В этой реализации для каждого типа перехватываемых исключений помимо ф-ции-обработчика необходимо по 2 дополнительные ф-ции — push_nandler и pop_handler.
В моем варианте этого не требуется.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.