Подтвердить что ты не робот

Как я могу абстрагировать повторяющийся шаблон 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 и код как одну абстракцию, но я не могу придумать хороший способ сделать это в С++ без макросов. Я бы подумал, что с шаблонами есть способ сделать это лучше.

Пожалуйста, предложите чистый способ рефакторинга этого кода, чтобы абстрагировать повторяющийся шаблон.

4b9b3361

Ответ 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, в зависимости от целевой аудитории и источника потенциальных программистов будущего. Иногда для достижения определенных целей требуется умное кодирование. Пример вашего примера?