суббота, 26 марта 2016 г.

[prog.flame] Java-программисты боятся, что в Java добавят ключевое слово var

Все-таки люди, программирующие на Java, имеют особый склад ума (что, в принципе, ожидаемо, учитывая свойства этого языка программирования). Иначе сложно объяснить волнения, вызванные намерениями добавить в Java ключевое слово var (например, вот статья с попытками рассмотреть доводы "за" и "против" на Хабре и уже не маленькое обсуждение на LOR-е).

Странные люди. Смотреть нужно в первую очередь на то, что можно получить от автоматического вывода типа локальной переменной. Насколько я помню, именно необходимость такого вывода стала причиной добавления var-ов в C#, т.к. без этого реализация и использование LINQ вряд ли были бы возможны. Да и в C++11, после добавления в язык variadic templates и lambdas без auto уже никуда. Ну вот серьезно, сейчас в C++ я могу написать вот так:

std::thread first;
std::thread second;
std::thread third;
auto thr_joiner = so_5::auto_join(first, second, third);

И, на мой взгляд, это гораздо лучше, чем заставлять программиста писать вот так:

std::thread first;
std::thread second;
std::thread third;
so_5::threads_auto_joiner<3> thr_joiner = so_5::auto_join(first, second, third);

Еще веселее дела обстоят с лямбда-функциями, для которых тип генерируется самим компиляторам и который неизвестен программисту. Ключевое слово auto позволяет вот так:

std::FILE * f = std::fopen(file_name, "r");
if( f ) {
   auto f_closer = at_scope_exit([f]{ std::fclose(f); });
   ...
}

А если бы его не было, как бы пришлось извращаться? Писать что-то вроде:

std::FILE * f = std::fopen(file_name, "r");
if( f ) {
   at_exit_t< std::function<void()> > f_closer = at_scope_exit([f]{ std::fclose(f); });
   ...
}

Как говорится, нет уж, спасибо ;)

Понятное дело, что автоматический локальный вывод типов способен усложнить понимание кода. Но это уже плата за расширение возможностей. Ведь даже в показанных выше очень простых примерах без var/auto сложно, что уж говорить про более сложные случаи с трехэтажными шаблонами.

Так что, на мой взгляд, очень странные обсуждения ведутся в стане Java-программистов. Впрочем, там язык настолько убог, что может var просто поздно добавлять... :)


Касательно auto в C++. До тех пор, пока не работаешь плотно с навороченными шаблонными конструкциями, к auto относишься настороженно. Ведь, действительно, код, в котором сплошные auto, прочитать сложнее, чем код со всеми аннотациями типов. Однако, даже если речь идет о более-менее простых ситуациях, без шаблонных наворотов, то оказывается, что auto может повысить качество и безопасность кода. Взять, скажем, совсем свежий пример:

char *path_name(const struct name_path *path, const char *name)
{
        const struct name_path *p;
        char *n, *m;
        int nlen = strlen(name);
        int len = nlen + 1;

Ведь будь он написан вот в таком виде:

char *path_name(const struct name_path *path, const char *name)
{
        const struct name_path *p;
        char *n, *m;
        auto nlen = strlen(name);
        auto len = nlen + 1;

Менее понятным он бы не стал. А вот надежнее -- наверняка.

Впрочем, более обстоятельно эту тему раскрывают люди, разбирающиеся в C++ гораздо лучше меня.

четверг, 24 марта 2016 г.

[prog.c++11] Дальнейшая диверсификация SObjectizer?

С момента появления mchain-ов в версии 5.5.13 возможности SObjectizer-а по поддержке CSP-like concurrency потихонечку расширяются. Так, в грядущей версии 5.5.16 mchain-ы станут полноценными MPMC-каналами (т.е. сразу несколько нитей смогут читать сообщения из mchain-а). Плюс, конечно же, функция select, которая позволяет выбирать сообщения сразу из нескольких mchain-ов.

В связи с этим уже неправильно позиционировать SObjectizer как только лишь actor framework. Т.к. кроме actor model из коробки уже имеется поддержка и CSP channels.

А раз так, может пойти и еще дальше? И добавить в SObjectizer поддержку цепочек задач? Что-то вроде:

void initiate_image_processing(so_5::environment_t & env, image_params params)
{
   // Готовим цепочку задач и привязываем ее к уже созданному
   // thread_pool-диспетчеру с именем "tasks_pool".
   schedule( env,
      so_5::disp::thread_pool::create_disp_binder("tasks_pool", ...),
      make_task( env, [params]() -> so_5::task< loaded_image > {
         ... // Какие-то действия по загрузке картинки.
         return loaded_image{ ... };
      })
      .then([]( loaded_image & img ) -> so_5::task< prepared_image > {
         ... // Какая-то нехилая обработка.
         return prepared_image{ ... };
      })
      .then([]( prepared_image & img ) -> so_5::task< converted_image > {
         ... // Какая-то еще более нехилая обработка.
         return converted_image{ ... };
      })
      .then([params]( converted_image & img ) {
         ... // Ну совсем замороченная обработка.
         so_5::send< transformed_image >(params.dest(), ...);
      }) );
}

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

Понятное дело, что глубоко в эту тему я не копал. Но, думается, можно сделать что-то вроде Data Flow и Dependency Graphs из Intel-овской Threading Building Blocks или Task Parallelism из Microsoft-овской PPL. Вопрос только в том, нужно/интересно ли это кому-нибудь?

PS. Возможно, все это будет логичным продолжением прототипа для реактивного программирования средствами SO-5, только в другом синтаксисе.

среда, 23 марта 2016 г.

[prog.c++11] Первый релиз-кандидат SObjectizer-5.5.16

Зафиксирован первый релиз-кандидат версии 5.5.16 проекта SObjectizer. Версия 5.5.16, в основном, расширяет возможности mchain-ов: теперь один и тот же mchain можно спокойно использовать в нескольких receive на разных нитях (в предшествующих версиях это могло привести к засыпанию некоторых нитей, если они вызвали receive когда mchain был пуст). Реализована функция select, которая позволяет ожидать сообщения сразу из нескольких mchain-ов. Плюс еще несколько мелочей.

Более подробно изменения будут описаны в Wiki проекта в ближайшие дни. После чего и состоится официальный релиз.

Взять 5.5.16-rc1 можно из репозитория (либо Svn на SourceForge, либо Git на GitHub).

Соответственно, если у кого-то будут замечания/предложения по текущей реализации, то не стесняемся, высказываем. Есть возможность вносить изменения до релиза. После релиза будет сложнее, т.к. сохранение совместимости между версиями внутри ветки 5.5 -- это одна и важных целей.

суббота, 19 марта 2016 г.

[prog.c++] Голые нити и exception safety

Раз уж на неделе затронул вопрос осторожного обращения с голыми нитями, то можно осветить еще один момент. Касающийся обеспечения exception safety при работе с многопоточностью.

В качестве демонстрации буду использовать вот такую небольшую программку:

[prog.c++] Продолжаем упарываться шаблонами при парсинге бинарных PDU

Продолжение вчерашней заметки.

Во вчерашнем решении очень не понравилось то, что смещение поля в PDU приходилось дублировать при вызовах encode и decode:

class header16_t
   :  protected data_with_encoder_and_decoder_t< 16 >
{
public :
   auto f1() const { return decode< std::uint16_t >( 0 ); }
   auto f2() const { return decode< std::uint16_t >( 2 ); }
   auto f3() const { return decode< std::uint16_t >( 4 ); }
   auto f4() const { return decode< std::uint16_t >( 8 ); }

   void set_f1( std::uint16_t v ) { encode( 0, v ); }
   void set_f2( std::uint16_t v ) { encode( 2, v ); }
   void set_f3( std::uint16_t v ) { encode( 4, v ); }
   void set_f4( std::uint16_t v ) { encode( 8, v ); }
};

Легко ошибиться. Собственно, я и ошибся: при вызове decode задал правильные смещения, а вот при вызове encode записал неправильные значения. Причем код с этой ошибкой ушел в заметку в блоге и только спустя какое-то время эта ошибка была замечена и исправлена.

Поэтому возник вопрос: а можно ли как-то однократно увязать смещение поле с описанием поля? Чтобы затем смещение автоматически извлекалось из описания поля когда это нужно.

Можно.

[comp] Лучи ненависти Asus-у :(

Asus-овский Zenbook, конечно, классный ноутбук. Но с одним фатальным недостатком: самопроизвольные выключения. Как правило, во время набора текста.

Проявляться эта проблема начала где-то месяца через 4 после покупки. Возникала в непредсказуемые моменты времени где-то раз в три недели. Иногда реже. Но иногда и чаще, вплоть до пары раз в день. Как сегодня. С учетом того, что предсказать появление проблемы нельзя, смысла везти в сервис и объяснять, что время от времени ноут отключается, не вижу. Тем более, что везти нужно будет в Москву и оставлять в сервисе на две-три-четыре недели. Тем более, что в Интернете уже много жалоб на такое поведение Zenbook-ов, а вот историй про то, что ноут смоли исправить в ремонте -- намного меньше.

В общем, пичаль-пичаль. Тем более, что не понятно, что можно взять на замену. Такое впечатление, что абсолютно все производители WIntel-овских ноутбуков в последние годы скатились в унылое говно.

пятница, 18 марта 2016 г.

[prog.c++14] Еще один кейс для использования шаблонов и CRTP

Разбираясь с примерами одной C++ной библиотеки обнаружил наличие в разных примерах с парсингом бинарных PDU дублирование в классах для PDU дублирование одних и те же методов encode и decode приблизительно вот такого вида:

void encode( std::size_t index, std::uint16_t v )
{
   auto b = &(rep()[index]);
   b[0] = static_cast< std::uint8_t >(v >> 8);
   b[1] = static_cast< std::uint8_t >(v & 0xffu);
}
void decode( std::size_t index, std::uint16_t & v ) const
{
   auto b = &(rep()[index]);
   v = (static_cast< std::uint16_t >(b[0]) << 8) + b[1];
}

Поскольку не люблю копипасту, то задумался, как можно сделать так, чтобы классы для представления PDU с разного размера payload-ами, имели бы общую реализацию методов encode/decode.

Под катом небольшой пример с результатами эксперимента. Код без комментариев, но, надеюсь, он будет понятен. Если же нет, то отвечу на вопросы в комментариях. Однако, сразу поясню пару вещей, которые могут быть не очевидны.

Сделано два класса encoder_t и decoder_t. Это для того, чтобы можно было делать PDU, которые можно только распаковывать, но не нужно упаковывать. Или, наоборот: PDU, которые нужно только упаковывать, но не распаковывать. Ну и, кроме того, так задачка была сложнее :)

Вот такую конструкцию пришлось сделать для того, чтобы сохранить методы data(), encode() и decode() скрытыми (protected).

template< std::size_t N >
class data_with_encoder_and_decoder_t
   :  public data_holder_t< N >
   ,  public encoder_t< data_with_encoder_and_decoder_t< N > >
   ,  public decoder_t< data_with_encoder_and_decoder_t< N > >
{};

Без этого пришлось бы кроме наследования от encoder_t/decoder_t еще и прописывать эти классы в качестве френдов. Что-то вроде:

class header16_t
   :  protected data_holder_t< 16 >
   ,  protected encoder< header16_t >
   ,  protected decoder< header16_t >
{
   friend class encoder< header16_t >;
   friend class decoder< header16_t >;

Вместо намного более короткого варианта:

class header16_t
   :  protected data_with_encoder_and_decoder_t< 16 >

Итак, вот полный код эксперимента: