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.
Я задал очень похожий концептуальный вопрос, см. Допустимо ли повторное генерирование исключения во вложенной «попытке»? .
По сути, вы можете переместить различные обработчики исключений в отдельную функцию, перехватывая все исключения, вызывая обработчик и повторно генерируя активное исключение.
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
Одним из вариантов, если количество функциональных арностей ограничено, было бы использование шаблона функции:
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());
}
В стороне, если вы поймаете (. ..)
и не вызывайте повторно перехваченное исключение, в большинстве случаев вам следует завершить программу.
У меня нет ответа, кроме как предположить, что вам может быть лучше вообще отказаться от обработки исключений и вместо этого полагаться на Smart Pointers и Boost Scope Exit , чтобы очистить все ваши ресурсы. Таким образом, вам не придется перехватывать исключения, если вы не сможете что-то с ними сделать, что бывает редко. Затем вы можете выполнять всю обработку исключений в одном централизованном месте выше по цепочке вызовов для отчетов об ошибках и тому подобного.
В качестве варианта решения Александра Гесслера вы можете опустить некоторые фигурные скобки, которые делают эту реализацию немного длинной. Он делает то же самое, только с немного меньшим количеством {} слов.
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;
}
Мой ответ концептуально похож на ответ Джеймса Макнеллиса, за исключением того, что я использую 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 так как это может помешать встраиванию.
Мой ответ: вообще ничего не делать. Первый пример кода, как показано, в порядке. Так что там повторение? Это просто и ясно, и делает то, что кажется. Это можно понять без дополнительных умственных усилий, помимо увиденного кода и общих знаний C++.
Подумайте, почему вы задали этот вопрос.
Я пришел из прошлых проектов, где код должен был быть проверен другими — не экспертами в области компьютерных наук, а федеральными инспекторами, инженерами-механиками, программистами-самоучками, учеными и т. д. Умные люди, все они ( или большинство из них), но только заядлый хромированный доктор наук или молодой программист, стремящийся произвести впечатление на всех своим высоким IQ, оценят умные «решения» этого вопроса. В тех местах, где я был, ничто не сравнится с простым ясным кодом, который делает то, что он говорит, без умственной нагрузки, связанной с необходимостью помнить о значении десятков классов, макросов и т. опытные инженеры-программисты.
Я все чаще обнаруживаю, что код на C++ (а также на Java и C#) становится все более сложным с точки зрения кодирования, но требует понимания неспециалистами по C++.
Конечно, YMMV, в зависимости от целевой аудитории и потенциальных будущих программистов. Иногда умное кодирование необходимо для достижения определенных целей. Ваш случай пример?
Используйте 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;
};