Последние пару месяцев плотно работаю с C++14 (на том уровне, который поддерживается в GCC-5.2 и 5.3). Конечно же C++14 -- это небольшое улучшение над C++11, но в общем и целом, если сравнивать с C++03, современный C++ является совершенно другим языком. Уж не знаю, как C++14 ощущается в задачах, где нужно выжимать такты из битов и наоборот. Но вот у меня сейчас C++ что-то вроде клеевого языка, основная задача которого экономить ресурсы, коих на целевых машинах не то, чтобы уж много. И ощущается C++14 в таком контексте практически так же, как какой-нибудь Ruby или Python. С приблизительно такими отличиями:
- для C++ не так уж много хороших и живых библиотек, а те, что есть, нужно подключать в свой проект сильно по разному (впрочем, здесь MxxRu::externals ну очень сильно упрощает жизнь);
- проходит гораздо больше времени между внесениями изменений в код и первым запуском измененного кода. В Ruby, например, не нужно ждать, пока проект с туевой хучей шаблонов из header-only библиотек скомпилируется и слинкуется;
- но уж если C++ный код скомпилировался, то не приходится ожидать вылета ошибки из-за несовпадения типов переменных или еще какой-нибудь мелочевки, которая в языках со статической компиляцией вылавливается на стадии компиляции, а в динамических языка -- в run-time;
- если уж твой C++ный код тормозит, то только из-за тебя :)
В общем, если затащить в свой проект качественные библиотеки и есть возможность пользоваться нормальным компилятором, то жить с C++11/14 легко и весело. Особенно если увлечься шаблонами и auto, то C++ный код по читабельности для меня оказывается ну очень похожим на Ruby-новый код: точно так же сначала все просто и очевидно, а через пару недель чешешь голову стараясь понять, какой же тип у этого выражения ;) В качестве демонстрации покажу ниже небольшой фрагментик (что называется, "из недавнего").
Но к чему я это все? А к тому, что оглядываясь по сторона возникает некоторое недоумение. Иногда задумываешься: куда же катится этот мир? Взять, например, русскоязычные профильные форумы. Мало того, что активность вокруг C++ там не такая уж и активная. Так еще и вопросы такие всплывают, что невольно задаешься вопросом: а вменяемые C++ники вообще остались? Глядя на вот такое (или такое, или такое) напрашивается вывод о том, что нас практически-то и не осталось. Если же задуматься еще и о том, что за такие художества кому-то еще и деньги платят, то как-то совсем грустно...
А еще давеча в почтовом ящике письмо из Boost-announce нашел, с темой "Rebooting Boost: Call for Lightning Talks @ CppCon". Вот, процитирую оттуда:
Recent and recurrent boost-dev discussions have raised the lack of cmake based tooling; lack of ABI management and the consequent ODR violation when mixing Boost versions in the same process; the anti-social behaviour of Boost towards library end users, new ideas, new blood and the wider C++ community; and the chronic lack of maintenance of up to half the Boost libraries.
One question which needs to be answered is whether a clean reboot of Boost from the first principles of proving high quality C++ libraries well suited for use with the latest C++ standard is viable. This new collection of C++ libraries would be started completely from scratch (even if some existing libraries were ported into the new organisation), so ANYTHING is possible.
Т.е. у Boost-а куча проблем, поэтому давайте-ка подумаем, как начать все заново.
Стало еще грустнее. На мой взгляд, Boost-оводы сделали много хорошего, но за это придется платить слишком большую цену. Подход, при котором из Boost-а пытаются сделать централизованную коллекцию высококлассных библиотек, был спорным с самого начала. Но раньше он хотя бы был оправдан тем, C++ развивался крайне медленно и в Boost-е обкатывались многие базовые вещи, которые следовало бы иметь в языке гораздо раньше.
Теперь же этот подход с единым сборищем библиотек только вредит. И если Boost-овы решив начать с начала пойдут по тому же самому пути, то они еще раз наступят на те же самые грабли.
Уже неоднократно говорил о том, что C++у нужно брать на вооружение идеи из других языков. RubyGems из Ruby или Cargo и Rust-а. Вот что нужно C++ сейчас, а не монстроузный Boost. Но, к сожалению, только у Boost-а достаточный авторитет, чтобы сделать какую-то систему управления зависимостями для C++ де-факто стандартом.
В общем, несколько досадно, что в кои-то веки C++ стал действительно хорошим и удобным языком. А вот инфраструктура вокруг него как была болотом, так и осталась.
Ну а теперь обещанные слайды :)
Это довольно большой фрагмент кода из реального проекта. Тут нужно запустить внешний процесс, который отвечает за конкретную задачу, затем проанализировать чем же завершилась работа этого процесса. В случае каких-то непредвиденных ошибок выбрасываются исключения, которые перехватываются и должным образом обрабатываются наверху. Ну и некоторые детали опущены как несущественные. Тем не менее, большая половина кода, относящаяся к запуску процесса и обработки его результатов, показана.
Интересно, как этот код воспринимается не C++никами. Для меня, например, он мало отличается от того, чтобы я написал для такой же задачи на Ruby (разве что в Ruby синтаксис был бы другой).
template< typename EXIT_WAITER > void try_generate_new_exception( // Функтор, который будет ждать завершения работы дочернего процесса. EXIT_WAITER exit_waiter, // Исключение, которое выскочило при работе процесса. const std::exception & x, // Сам процесс. procxx::process & tool ) { // Попытаемся вычитать поток ошибок от внешнего инструмента. auto stderr_content = load_tool_stderr( tool ); // Нужно дождаться завершения процесса, чтобы у нас была возможность // залогировать его код возврата. auto exit_code = exit_waiter(); // Если процесс завершился с ненулевым кодом возврата, то нужно // сгенерировать новое исключение, в котором этот факт будет отражен. if( exit_code ) { auto exception_message = stderr_content.empty() ? fmt::format( "non-zero exit code: {}, no stderr, " "associated exception: [{}]", exit_code, x.what() ) : fmt::format( "non-zero exit code: {}, stderr: [{}]", exit_code, stderr_content ); throw std::runtime_error( std::move(exception_message) ); } } template< typename DEADLINE_MAKER > auto launch_tool_and_parse_output( DEADLINE_MAKER deadline_maker, procxx::process & tool, tool_output_parser_t & parser, spdlog::logger & logger ) { auto wait_and_trace_exit_code = [&] { tool.wait(); logger.trace( "tool finished [exit_code={}]", tool.code() ); return tool.code(); }; tool.exec(); // Сразу после запуска начинаем контролировать время жизни // запущенного процесса. auto tool_deadline = deadline_maker( tool.id() ); try { parser.parse( tool.output(), [&logger]( const auto & line ) { logger.trace( "tool_stdout: {}", line ); } ); } catch( const std::exception & x ) { // Если процесс был завершен принудительно, то дальнейшая обработка // не нужна. if( deadline_status_t::process_finished_itself != tool_deadline.status() ) { wait_and_trace_exit_code(); throw std::runtime_error( "tool killed because of deadline" ); } // Ну а раз процесс завершился самостоятельно, то имеет смысл // прочитать его поток ошибок и разобраться с кодами возврата. try_generate_new_exception( wait_and_trace_exit_code, x, tool ); // У tool нулевой код возврата и мы не знаем, что делать. // Пусть с исключением разбираются наверху. throw; } catch( ... ) { // Неожиданно... Но нужно дождаться завершение процесса и залогировать // его код возврата. wait_and_trace_exit_code(); // После чего с исключением пусть разбираются выше. throw; } // Если оказались здесь без ошибок, значит процесс отработал нормально. // Но дожаться его завершения все равно нужно. return wait_and_trace_exit_code(); } void a_action_performer_t::run_child_process_and_collect_results( const msg_ns::new_action & action, msg_ns::action_result & result ) { m_logger.trace( "initiating action, [action_id={}][max_total_time={}s]", result.m_action_id, action.m_max_total_time ); // Подготавливаем процесс к запуску... auto tool = prepare_tool_process( action, result.m_action_id ); // ...собираем и парсим выхлоп... tool_output_parser_t parser( result.m_action_id ); auto exit_code = launch_tool_and_parse_output( [&]( pid_t pid ) { const auto deadline = calc_tool_deadline( action ); return process_deadline_t( so_environment(), m_child_deadliner, pid, deadline, fmt::format( "[action_id={}][deadline={}s]", result.m_action_id, deadline.count() ) ); }, *tool, parser, m_logger ); // Переносим в результат те значения, которые удалось разобрать. fill_result( exit_code, parser.result(), result ); } |
Комментариев нет:
Отправить комментарий