Как я могу абстрагировать повторяющийся шаблон catch try в С++
У меня есть шаблон, который повторяется для нескольких функций-членов, которые выглядят следующим образом:
int myClass::abstract_one(int sig1)
{
try {
return _original->abstract_one(sig1);
} catch (std::exception& err) {
handleException(err);
} catch (...) {
handleException();
}
}
bool myClass::abstract_two(int sig2)
{
try {
return _original->abstract_two(sig2);
} catch (std::exception& err) {
handleException(err);
} catch (...) {
handleException();
}
}
[...]
int myClass::abstract_n(bool sig3a, short sig3b)
{
try {
return _original->abstract_n(sig3a, sig3b);
} catch (std::exception& err) {
handleException(err);
} catch (...) {
handleException();
}
}
Где абстрактные с помощью n - это методы чистого виртуального абстрактного интерфейса, для которых myClass и _original являются конкретными реализациями.
Мне не нравится, что этот шаблон повторяется в коде и хотел бы найти способ устранить повторяющийся шаблон try/catch и код как одну абстракцию, но я не могу придумать хороший способ сделать это в С++ без макросов. Я бы подумал, что с шаблонами есть способ сделать это лучше.
Пожалуйста, предложите чистый способ рефакторинга этого кода, чтобы абстрагировать повторяющийся шаблон.
Ответы
Ответ 1
Я задал очень похожий концептуальный вопрос, см. Является ли повторное металирование исключений законным во вложенной "попытке" ?.
В принципе, вы можете переместить различные обработчики исключений в отдельную функцию, перехватив все исключения, вызов обработчик и перевернув активное исключение.
void handle() {
try {
throw;
} catch (std::exception& err) {
handleException(err);
} catch (MyException& err) {
handleMyException(err);
} catch (...) {
handleException();
}
}
try {
return _original->abstract_two(sig2);
} catch (...) {
handle();
}
Он отлично масштабируется с разными видами исключений, чтобы различаться. Вы можете упаковать первый try .. catch(...)
в макросы, если хотите:
BEGIN_CATCH_HANDLER
return _original->abstract_two(sig2);
END_CATCH_HANDLER
Ответ 2
Один из вариантов, если существует ограниченное количество функций, - это использовать шаблон функции:
template <typename ReturnT, typename ClassT>
ReturnT call_and_handle(ClassT* obj, ReturnT(ClassT::*func)())
{
try {
return (obj->*func)();
}
catch (const std::exception& ex) {
handleException(ex);
}
catch (...) {
handleException();
}
return ReturnT();
}
Это предполагает, что handleException
- некоторая функция, не являющаяся членом, но ее легко изменить, если она является функцией-членом. Вам нужно решить, что call_and_handle
возвращает, если обрабатывается исключение; Я возвращаю инициализированный ReturnT
в качестве заполнителя.
Это уменьшает ваши функции-члены до:
int myClass::abstract_one()
{
return call_and_handle(_original, &myClass::abstract_one);
}
Вам понадобится отдельный шаблон функции для вызова функций с одним параметром, двумя параметрами и т.д.
Если у вас есть функции с громоздким количеством параметров, и вы действительно отчаялись, вы могли бы использовать макрос (я бы этого не рекомендовал):
#define CALL_AND_HANDLE(expr) \
try { \
return (expr); \
} \
catch (const std::exception& ex) { \
handleException(ex); \
} \
catch (...) { \
handleException(); \
}
который может использоваться как:
int myClass::abstract_one()
{
CALL_AND_HANDLE(_original->myClass::abstract_one());
}
В качестве стороннего, если вы catch (...)
и не реконструируете пойманное исключение, вы должны в большинстве случаев прекратить выполнение программы.
Ответ 3
В качестве варианта решения Александра Гесслера вы можете опустить некоторые фигурные скобки, которые делают эту реализацию немного длинной. Он делает то же самое, только с немного меньшим {} verbage.
void handle() try
{
throw;
}
catch (std::exception& err)
{
handleException(err);
}
catch (MyException& err)
{
handleMyException(err);
}
catch (...)
{
handleException();
}
int myClass::abstract_one(int sig1) try
{
return _original->abstract_one(sig1);
}
catch (...)
{
handle();
return -1;
}
Ответ 4
Мой ответ концептуально похож на Джеймса Макнеллиса, за исключением того, что я использую boost:: bind для тяжелой работы:
using boost::bind;
class myClass
{
public:
myClass(origClass * orig) : orig_(orig) {}
int f1(bool b) { return wrapper(bind(&origClass::f1, orig_, b)); }
bool f2(int i) { return wrapper(bind(&origClass::f2, orig_, i)); }
void f3(int i) { return wrapper(bind(&origClass::f3, orig_, i)); }
private:
origClass * orig_;
template <typename T>
typename T::result_type wrapper(T func)
{
try {
return func();
}
catch (std::exception const &e) {
handleError(e);
}
catch (...) {
handleError();
}
}
};
Обратите внимание, что здесь я бы не использовал функцию boost::, поскольку это может помешать вложению.
Ответ 5
У меня нет ответа, кроме как предположить, что вам может быть лучше избегать обработки исключений вообще и вместо этого полагаться на "Умные указатели" и "Увеличивать объем действия" , чтобы очистить весь ваш ресурс. Таким образом, вам не придется ломать исключения, если вы не можете что-то сделать с ними, что редко бывает. Затем вы можете выполнять обработку исключений в одном централизованном месте выше, чем цепочка вызовов для сообщений об ошибках и т.д.
Ответ 6
Используйте boost:: function и boost:: bind. Работает с любой сигнатурой функции, пока соответствует тип возвращаемого значения;
#include <boost/function.hpp>
#include <boost/bind.hpp>
using boost::function;
using boost::bind;
template<typename T>
T exception_wrapper(boost::function<T()> func)
{
try {
return func();
} catch (std::exception& err) {
handleException(err);
} catch (...) {
handleException();
}
}
// ways to call
int result = exception_wrapper<int>(bind(libraryFunc, firstParam));
// or a member function
LibraryClass* object;
result = exception_wrapper<int>(bind(&LibraryClass::Function, object, firstParam));
// So your wrapping class:
class YourWrapper : public SomeInterface
{
public:
int abstract_one(int sig1)
{
return exception_wrapper<int>(bind(&LibraryClass::concrete_one, m_pObject, sig1));
}
bool abstract_two(int sig1, int sig2)
{
return exception_wrapper<bool>(bind(&LibraryClass::concrete_two, m_pObject, sig1, sig2));
}
// ...
private:
LibraryClass* m_pObject;
};
Ответ 7
Мой ответ: ничего не делать. Первый пример кода, как показано, прекрасен. Так что же такое повторение? Это ясно и ясно, и делает то, что кажется. Это можно понять, не испытывая лишних умственных нагрузок от увиденного кода и общего знания о С++.
Подумайте о своем мотиве, чтобы задать этот вопрос.
Где я иду из прошлых проектов, где код должен был быть рассмотрен другими - не PhD Comp Sci, а федеральные инспекторы, инженеры-механики, программисты-самоучки, ученые и т.д. Умные люди, все они ( или большинство из них), но только глухой кумир PhD или младший программист, чтобы поразить всех своим привет-IQ, по достоинству оценят умные "решения" этого вопроса. Для тех мест, где я был, ничто не сравнится с простым ясным кодом, который делает то, что он говорит, без материального бремени иметь в виду значение десятков классов, макросов и т.д. И признавать "шаблон проектирования", который правильно понимается только опытных инженеров-программистов.
Все чаще я нахожу код С++ (и Java и С#) более сложным в смысле кодирования, но его нужно понимать неспециалистами на С++.
Конечно, YMMV, в зависимости от целевой аудитории и источника потенциальных программистов будущего. Иногда для достижения определенных целей требуется умное кодирование. Пример вашего примера?