Comments 22
Так ведь и статья не о том, как обрабатывать исключения. А о том, как при чтении кода быстро понять, что следует вызывать только внутри try. Ну и о том, как получить хотя бы некоторую помощь в этом со стороны компилятора.
Поэтому в статье нет ни одного реального примера try и catch. Хотя о том, что catch-и с разными действиями в проекте присутствуют в статье упоминается.
Мне кажется, было бы проще обязать всех ставить к-нить макросы вокруг коллбэков, вроде
BEGIN_EXCEPTION_HANDLER {
// work
} END_EXCEPTION_HANDLER
Гораздо проще, имхо, обязать всех не передавать callback-и в Asio напрямую, а вызывать специальный метод для создания обертки вокруг callback-а (а сами прикладные действия отдавать в этот метод посредством лямбды). Что, собственно, у нас и происходит.
Но т.к. мы не можем с помощью компилятора проверить, что все callback-и создаются именно таким образом (а писать какие-то плагины для компилятора или делать собственные анализаторы на базе clang-а в наши планы не входит), то захотелось иметь дополнительную степень безопасности. Если так можно сказать.
Так же и с макросами BEGIN_EXCEPTION_HANDLER/END_EXCEPTION_HANDLER. Обязать их расставлять можно. А вот как автоматически контролировать выполнение этого требования?
А как глазами выцепить, что в функции нужен лишний параметр, я даже не представляю
Так C++ же статически типизированный язык. Если каких-то параметров не будет хватать или у них типы не совпадают, то компиляция не пройдет.
optional<S> parse(json) {
S result;
if (!json.contains("id"))
return nullopt;
result.id = json.get<int>("id");
if (!json.contains("value"))
return nullopt;
result.value = std::stol(json.get<string>("value"));
if (!json.contains("type"))
return nullopt;
result.type = json.get<string>("type");
return result;
}
Нужно ли в него вставлять этот дополнительный параметр?
Э… Вы ставите меня в тупик. Я не понимаю, что это за функция и какое отношение она имеет к описанному в статье.
Могу предположить, что вам нужно вызвать такую функцию из какого-то callback-а, который отдается в Asio. Если так, то все будет зависеть от того, что еще делается в callback-е.
Например, у нас это могло бы выглядеть так:
m_connection.async_read_some(asio::buffer(m_data),
with<const asio::error_code &, std::size_t>().make_handler(
[this](can_throw_t can_throw,
const asio::error_code & ec,
std::size_t bytes_transferred)
{
if(!ec) {
m_data_size = bytes_transferred;
auto json = try_extract_payload();
auto data = parse(json);
}
});
То, в принципе, в parse
необязательно добавлять еще один аргумент. Т.к. parse уже вызывается в контексте, где try/catch есть.
Однако, если вы все-таки в parse добавите параметр can_throw, то даже если вы (или тот, кто будет дописывать код после вас) вдруг напишите async_read_some без создания специального врапера:
m_connection.async_read_some(asio::buffer(m_data),
[this](const asio::error_code & ec,
std::size_t bytes_transferred)
{
if(!ec) {
m_data_size = bytes_transferred;
auto json = try_extract_payload();
auto data = parse(json);
}
});
То компилятор даст вам по рукам.
Asio — это вовсе не легаси. И Asio сам может выбрасывать исключения. Но вот Asio понятия не имеет, что делать с исключениями, которые вылетают из completion-handler-а. Следовательно, исключения из переданных в Asio completion-handler-ов выпускать нельзя.
Нам приходится использовать чужие библиотеки на C, которые, в принципе, можно назвать легаси. Но заменить которые вот так просто возможности не представляется. Поэтому нам нужно писать callback-и в C++, а вызывать их будут из C. Куда C++ные исключения отдавать нельзя.
Поэтому приходится жить с тем, что есть. И, раз уж приходится так жить, то хорошо бы хоть немного защититься от хождения по случайно забытым граблям.
— нельзя забыть про эксепшионы
— наш код чистый
— profit :)
А в вашем случае вы переложили контроль границы на все места перехода этой границы, и ясное дело что это еще и дублирование кода контроля, в котором можно ошибиться, или вообще не неапсать.
Например, функция asio, принимающая каллбэк — делаем обвертку в которой оборачиваем пришедший каллбэк своим кодом, ловящим наши эксепшионы.
Делать обертки вокруг чужих библиотек — так себе занятие. Вот та же Asio. Непростая библиотека с продвинутым API сама по себе. И, по вашему, нам нужно сделать собственные обертки над asio::ip::tcp::socket и такими его методами, как async_read_some/async_write_some?
Делать обертки вокруг чужих библиотек — так себе занятие. Вот та же Asio. Непростая библиотека с продвинутым API сама по себе. И, по вашему, нам нужно сделать собственные обертки над asio::ip::tcp::socket и такими его методами, как async_read_some/async_write_some?
Asio действительно непростая библиотека, и в ней есть даже небольшая поддержка аспектно-ориентированного программирования, сосредоточенная как раз вокруг обработчиков. Так что оборачивать всё совсем не нужно, вызовы обработчиков можно контролировать. И это расширяемо в том плане, что не важно обработчик чего это — события на сокете, таймере или вообще кто-то написал дополнение для asio с асинхронной работой с какими-то другими объектами.
Правда, для этого обработчики должны иметь некий пользовательский тип. Но, по-моему, это и так всегда полезно — хоть для какой-то отладки вызова обработчиков, хоть для выделения памяти для максимальной производительности.
Так что оборачивать всё совсем не нужно, вызовы обработчиков можно контролировать.
Я не настолько хорошо владею Asio, чтобы понять, откуда берется my_handler в примере по ссылке:
class my_handler;
template <typename Function>
void asio_handler_invoke(Function function, my_handler* context)
{
context->strand_.dispatch(function);
}
Это будет указатель на экземпляр, который передается, скажем, в async_read_some? Типа такого:
struct my_read_handler {
void operator()(const asio::error_code & ec, std::size_t bytes) {...}
...
};
...
some_connection.async_read_some(asio::buffer(...), my_read_handler{...});
Это будет указатель на экземпляр, который передается, скажем, в async_read_some?
Да. Точнее, конечно, на копию переданного. Он нужен для разрешения перегрузки. А Function заботится о том, чтобы передать в него нужные аргументы (код ошибки, количество прочитанных байтов и т.д.).
Но вот Asio понятия не имеет, что делать с исключениями, которые вылетают из completion-handler-а. Следовательно, исключения из переданных в Asio completion-handler-ов выпускать нельзя.
Asio отнюдь не возражает против исключений из обработчиков, и относится к ним вполне толерантно.
Другое дело, конечно, что в catch блоке мы окажемся уж слишком оторванными от контекста, и будет сложно правильно среагировать — удалить какие-нибудь сессии/соединения или совершить попытку ещё раз.
Asio отнюдь не возражает против исключений из обработчиков, и относится к ним вполне толерантно.
А можно подробнее. Что значит "относится толерантно"?
can_throw или не can_throw?