Как я могу абстрагироваться от повторяющегося шаблона try catch в C ++? - PullRequest
20 голосов
/ 25 августа 2010

У меня есть шаблон, который повторяется для нескольких функций-членов, который выглядит следующим образом:

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

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

Ответы [ 7 ]

23 голосов
/ 25 августа 2010

Я задал очень похожий концептуальный вопрос, см. Допустимо ли повторное создание исключения во вложенной попытке? .

По сути, вы можете переместить различные обработчики исключений в отдельную функцию, перехватывая все исключения, вызывая обработчик и повторно выбрасывая активное исключение.

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
15 голосов
/ 25 августа 2010

Один вариант, если количество функций ограничено, было бы использовать шаблон функции:

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 голосов
/ 25 августа 2010

В качестве варианта решения Александра Гесслера вы можете опустить некоторые скобки, которые делают эту реализацию немного длинной. Он делает то же самое, только с небольшим количеством {} пустяков.

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;
}
2 голосов
/ 25 августа 2010

Мой ответ концептуально аналогичен Джеймсу МакНеллису, за исключением того, что я использую 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 ::это может помешать встраиванию.

1 голос
/ 27 августа 2010

Мой ответ: вообще ничего не делать.Первый приведенный пример кода в порядке.Так что же такое повторение?Это ясно и ясно, и делает то, на что похоже, что это делает.Это можно понять без лишних умственных нагрузок после увиденного кода и общего знания C ++.

Подумайте о своем мотиве задать этот вопрос.

Я пришел из прошлых проектов, в которых код должен был проверяться другими - не экспертами PhD Comp Sci, а федеральными инспекторами, инженерами-механиками.программисты-самоучки, ученые и т. д. Умные люди, все они (или большинство из них), но только первоклассный доктор наук по хромированным куполам, или более молодые программисты, чтобы поразить всех своим высоким IQ,был бы признателен за умные «решения» этого вопроса.Для тех мест, где я побывал, ничто не сравнится с простым и понятным кодом, который выполняет то, что говорит, без умственного бремени необходимости учитывать значение десятков классов, макросов и т. Д. И распознавать «шаблон проектирования», понимаемый толькоопытные инженеры-программисты.

Я нахожу, что код C ++ (и Java и C #) все более усложняется в смысле кодирования, но его не нужно понимать специалистам по C ++.

Конечно, YMMV, в зависимости отцелевая аудитория и источник потенциальных будущих программистов обслуживания.Иногда умное кодирование необходимо для достижения определенных целей.Ваш случай является примером?

1 голос
/ 25 августа 2010

Используйте 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;
};
1 голос
/ 25 августа 2010

У меня нет ответа, кроме как предположить, что вам лучше избегать обработки исключений в целом и полагаться вместо этого на Умные указатели и Boost Scope Exit , чтобы сделать весь ваш ресурс убирать Таким образом, вам не придется ловить исключения, если вы не можете что-то с ними сделать, что редко бывает. Затем вы можете выполнять всю обработку исключений в одном централизованном месте выше по цепочке вызовов для отчетов об ошибках и т. Д.

...