Повторное использование кода при обработке исключений

Я разрабатываю C api для некоторых функций, написанных на С++, и я хочу убедиться, что никакие исключения не распространяются ни на одну из экспортированных функций C.

Простой способ сделать это - убедиться, что каждая экспортированная функция содержится в файле:

try {
   // Do the actual code
} catch (...) {
   return ERROR_UNHANDLED_EXCEPTION;
}

Скажем, я знаю одно исключение, которое часто пропускается внутри кода С++, это std:: bad_alloc, и я хочу его обработать специально, я бы написал что-то вроде этого:

try {
   // Run the actual code
} catch (std::bad_alloc& e) {
   return ERROR_BAD_ALLOC;
} catch (...) {
   return ERROR_UNHANDLED_EXCEPTION;
}

Можно ли разложить это на какой-нибудь умный способ, чтобы я мог глобально обрабатывать некоторые ошибки по-разному, не добавляя новый оператор catch для обработчика исключений вокруг каждой экспортируемой функции?

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

Ответ 1

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

int HandleException()
{
    try 
    {
        throw;
    }

    // TODO: add more types of exceptions

    catch( std::bad_alloc & ) 
    {
       return ERROR_BAD_ALLOC;
    }
    catch( ... )
    {
        return ERROR_UNHANDLED_EXCEPTION;
    }
}

И в каждой экспортируемой функции:

try
{
    ...
}
catch( ... )
{
    return HandleException();
}

Ответ 2

Уже есть хороший ответ. Но только FYI, его идиома "исключение-диспетчер", см. С++ FAQ 17.15. IMHO 17.16 также является важным показателем.

Ответ 3

Как насчет:

try{
    //Your code here
} catch(std::exception e)
{
   return translateExceptionToErrorCode(e);
} catch(...)
{
   return UNKNOWN_EXCEPTION_THROWN;
}

Ответ 4

Jem ответ немного проще, чем это решение. Но можно заменить использование макроса препроцессора с использованием шаблонов. Что-то вроде этого (больше уточнений вы могли бы сделать):

template <class T, void (T::*FUNC)()>
class CatchWrapper
{
public:

    static void WrapCall(T* instance)
    {
        try
        {
            (instance->*FUNC)();
        }
        catch (std::bad_alloc&)
        {
            // Do Something 1
        }
        catch (std::exception& e)
        {
            // Do Something 2
        }
        catch (...)
        {
            // Do Something 3
        }
    }
};


class Foo
{
public:
    void SomeCall()
    {
        std::cout << "Do Something" << std::endl;
    }
};


int main(int argc, char* argv[])
{
    Foo i;
    CatchWrapper<Foo, &Foo::SomeCall>::WrapCall(&i);
    return 0;
}

Ответ 5

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

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

Ответ 6

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

Как вы это делаете, это зависит от того, как выглядят ваши классы исключений. Если вы контролируете свою иерархию классов исключений, вы можете убедиться, что каждый класс предоставляет перевод с использованием виртуального метода. Если нет, вы все равно можете найти практическое использование функции транслятора и протестировать типы получаемых исключений 'std:: exception', которые он получает, чтобы перевести их в код ошибки, так же, как предложил Jem (помните: выброшенные исключения будут повреждены производительность в любом случае, поэтому не беспокойтесь о медленном переводе).