How can I abstract out a repeating try catch pattern in C++

I have a pattern that repeats for several member functions that looks like this:

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();
  }
}

Where abstract one through n are methods of a pure virtual abstract interface for which myClass and _original are concrete implementations.

I don't like that this pattern repeats in the code and would like to find a way to eliminate the repeating try / catch pattern and code as a single abstraction, but I can't think of a good way to do this in C++ without macros. I would think that there is a way with templates to do this better.

Please suggest a clean way to refactor this code to abstract out the repeated pattern.

22
задан Guy Avraham 3 October 2018 в 12:54
поделиться

7 ответов

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

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

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
24
ответ дан 29 November 2019 в 04:23
поделиться

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

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());
}

В стороне, если вы поймаете (. ..) и не вызывайте повторно перехваченное исключение, в большинстве случаев вам следует завершить программу.

15
ответ дан 29 November 2019 в 04:23
поделиться

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

1
ответ дан 29 November 2019 в 04:23
поделиться

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

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;
}
3
ответ дан 29 November 2019 в 04:23
поделиться

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

2
ответ дан 29 November 2019 в 04:23
поделиться

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

Подумайте, почему вы задали этот вопрос.

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

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

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

1
ответ дан 29 November 2019 в 04:23
поделиться

Используйте 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
ответ дан 29 November 2019 в 04:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: