Иногда (например при состыковке библиотек) возникает подобный код:
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.
Жду критики
Здравствуйте, 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
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, 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.: Винодельческие провинции — это есть рулез!
Здравствуйте, Коваленко Дмитрий, Вы писали:
КД>Не, как раз это было просто и понятно. А вот то что было дальше, даже с двух стаканов не понять
Что именно — реализацию или использование ??
КД>Посмотри здесьАвтор: 0xDEADBEEF
Дата: 05.10.05
Интересное решение, жаль сам не нашел — сэкономил бы несколько часов времени.
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>А динамика где?
динамика — выбор перехватываемых исключений равно как и обработчиков в рантайме.
ГВ>И чем такая реализация лучше, например, такого кода:
ГВ>ГВ>//
ГВ>// Эти функции могут прикрывть афигенно сложный слой, управляющий обработкой ошибок.
ГВ>// Возвращают 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.
В моем варианте этого не требуется.