Try {} catch (...) {} в обратном вызове C - плохая идея?

Я реализую обратные вызовы в С++, которые будут вызываться из обычного кода C. Моя функция main() уже есть С++, но код C будет отвечать за создание потоков, которые в конечном итоге вызовут мои обратные вызовы.

Сейчас мои обратные вызовы выглядят как

int handle_foo(void *userdata) {
    try {
        MyCPPClass *obj = static_cast<MyCPPClass *>(userdata);
        obj->doStuff();
        return 0; // no error
    } catch(...) {
        LogError("doStuff failed"); 
        return -1; // error
    }
}

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

Является try {} catch(...) {} здесь разумным, или есть лучший способ написать мои C-обратные вызовы?

Ответ 1

Да, вы должны поймать исключения и, надеюсь, перевести их во что-то полезное. Предоставление исключений через C-код приводит к поведению undefined. В лучшем случае вы не можете ожидать, что код C сохранит согласованное состояние программы.

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

Кроме того, это имеет любую вероятность "работать", если весь код построен против той же среды выполнения С++. Если у вас есть обратный вызов, реализованный в скрипте Visual С++ 9, а остальная часть кода - в Visual С++ 10, или эти части скомпилированы против статических библиотек времени выполнения - теперь у вас есть две разные среды выполнения, а необработанное исключение в обратном вызове вызывает terminate() вызываемый.

Ответ 2

sharptooth ответил на весь ваш вопрос, и Джеймс Макнеллис написал хороший сообщение в блоге о том, как элегантно избавиться шаблона с использованием современного С++.

Суть его заключается в использовании функции translate_exceptions, например:

int callback(...) {
   return translate_exceptions([&]{
      // ... your code here
   });
}

Переведенный_exception - это простой шаблон функции (который использует блок try уровня функции), используя invokable:

template<typename Fn> int translate_exception(Fn fn) try {
  fn();
  return 0;
} catch(std::exception const& e) {
  LOG("[EE] exception ", e.what());
  return -2;
} catch( ... ) {
  LOG("[EE] unknown exception");
  return -1;
}