пятница, 21 ноября 2014 г.

[prog.c++] Про использование C++ных шаблонов при разработке timertt-1.1

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

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

Полагаю, что процесс преобразования timertt к поддержке как однопоточного, так и многопоточного варианта еще не закончен. Но какие-то работающие куски уже получены. Данный пост преследует цель попытаться лучше понять, что же именно было сделано, устраивает ли этот вариант и куда/как следует двигаться дальше. Ну и может быть кому-то будет интересно посмотреть на фокусы с шаблонами, на которые пришлось пойти, дабы минимизировать объем дублирующегося кода и избежать тупой копи-пасты.

четверг, 20 ноября 2014 г.

[prog.c++] Давно так плотно не работал с шаблонами, интересные впечатления

Очень давно так плотно не погружался в C++ные шаблоны, как в последние несколько дней. Вроде как ничего сложного, и нет такого, чтобы шаблон на шаблоне шаблоном погоняет. Тем не менее, что-то вроде вот такого:

template< typename ENGINE, typename CONSUMER >
class basic_methods_impl_mixin
   :  protected mixin_selector< typename ENGINE::thread_safety, CONSUMER >::type
   ,  public ENGINE::defaults_type
{

и вот такого:

template< typename THREAD_SAFETY, typename ERROR_LOGGER, typename ACTOR_EXCEPTION_HANDLER >
class timer_heap_manager_template
   : public details::manager_impl_template<
            details::timer_heap_engine<
                  THREAD_SAFETY,
                  ERROR_LOGGER,
                  ACTOR_EXCEPTION_HANDLER > > 
{

встречается регулярно :)

Проверяя написанное неоднократно ловил себя на мысли, что программирование на C++ных шаблонах -- это как программирование на динамически-типизированном языке вроде Ruby. Т.е. если не напишешь unit-тесты на все куски шаблонного класса, ничего о корректности своего кода сказать не можешь. Причем, если в коде нет реального инстанциирования шаблона, то некоторые компиляторы (не будем лишний раз тыкать палочкой в VC++, но...) пропускают чуть ли не синтаксические ошибки в коде :)

Еще одно впечатление: похоже, при написании кода шаблона самый лучший способ обращения к атрибутам и методам шаблонного класса -- это через this->. Т.е. во многих случаях можно писать так:

void
shutdown()
{
   typename base_type::lock_guard locker{ *this };

   if( m_thread && !m_shutdown )
   {
      m_shutdown = true;
      notify();
   }
}

Но, если часть атрибутов/методов со временем переезжает в базовый шаблонный класс, то для обращения к ним уже нужно использовать this:

void
shutdown()
{
   typename base_type::lock_guard locker{ *this };

   ifthis->m_thread && !this->m_shutdown )
   {
      this->m_shutdown = true;
      this->notify();
   }
}

Посему лучше сразу все писать через this, и не заниматься правкой кода и борьбой с ошибками компиляции при рефакторинге. Но если все писать через this->, то тогда нужно отказываться от префикса m_, который за последние пятнадцать лет стал уже как родной :(

Еще несколько расстраивает многословность шаблонов при их специализации. Т.е. сейчас приходится писать:

templatetypename THREAD_SAFETY, typename CONSUMER >
struct mixin_selector
{
   // Специально пустой, т.к. без наличия конкретной специализации
   // должна возникать ошибка компиляции.
};

template<>
struct mixin_selector< thread_safety::unsafe, consumer_type::manager >
{
   using type = thread_unsafe_manager_mixin;
};

template<>
struct mixin_selector< thread_safety::safe, consumer_type::manager >
{
   using type = thread_safe_manager_mixin;
};

template<>
struct mixin_selector< thread_safety::safe, consumer_type::thread >
{
   using type = thread_mixin;
};

Тогда как хотелось бы обойтись без лишних пустых деклараций. Что-то вроде:

template for<thread_safety::unsafe, consumer_type::manager>
struct mixin_selector
{
   using type = thread_unsafe_manager_mixin;
};

template for<thread_safety::safe, consumer_type::manager>
struct mixin_selector
{
   using type = thread_safe_manager_mixin;
};

template for<thread_safety::safe, consumer_type::thread>
struct mixin_selector
{
   using type = thread_mixin;
};

Только вот не уверен, насколько это возможно. И видит ли в этом недостаток еще кто-нибудь кроме меня :)

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

Лично мне в этой связи часто вспоминается появление Java. Сейчас мало кто помнит, но шаблоны появились в C++ в конце 1980-х, самом начале 1990-х (при том, что официальной датой рождения C++ считается 1986-й год). Конечно, массовой поддержки шаблонов в C++ных компиляторах не было, да и там, где она была, были сильные различия в качестве ее реализации. Однако факты именно таковы: Степанов в 1993-м уже демонстрировал миру свои наработки по STL на базе C++ных шаблонов. Тогда как сама Java в Sun-е в это время существовала в виде наработок в рамках Green- и Oak-проектов.

А решение о выпуске ее в свет, да еще под именем Java, было принято в 1994-м. После чего началась рекламно-пропагандистская компания по подготовке мира к принятию Java. Сколько тогда было многообещающих статей о том, что Sun выкинул из C++ все самое плохое, оставил все самое лучшее, да еще и добавил сборщик мусора и реальную бинарную кроссплатформенность. А что получилось по факту? Как по мне, как сильно кастрированное и унылое говно. Гослинг со товарищи правильно сделали, что выбросили препроцессор и совместимость с C. Но вот то, что они не захотели сделать в Java шаблоны, да еще тогда, когда уже было видно, что шаблоны -- это одна из самых мощных возможностей C++... Иначе как ниасиляторством обозвать это нельзя.

Собственно, я вполне хорошо понимаю людей, которые набили себе шишек на "C with classes" и первых версиях Java (а то и C#) и ушли в OCaml-ы с Haskell-ем. Но, сдается мне, далеко не всегда у них были хорошие знания нормального C++, который с шаблонами и исключениями. Впрочем, это уже совсем другая тема для разговора.

[prog.flame] Очередной молодой программист вопрошает "Ну когда же все будет делаться нормально?"

Небезызвестный в Рунете персонаж выдал очередной поток сознания: "Объясните дураку". Что забавно: для того, чтобы зацепить читателя и проиллюстрировать свою мысль, он приводит в пример плейеры, которые нормально не могут сохранить точку остановки воспроизведения. Но зачем так далеко ходить за примерами? У него в ЖЖ настолько угребищный дизайн, что в больших обсуждениях комментарии очень быстро становятся нечитаемыми :)

Что же до основного вопроса, т.е. когда же софт будет разрабатываться нормально и когда появятся инструменты, которые упростят этот процесс, то ответ прост и неутешителен: никогда.

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

Ну и хотя бы потому, что разработка софта -- это крайне зависящий от множества людей процесс. Как только люди поумнеют и научатся многим вещам, например, четко осознавать, что же именно они хотят и связно, понятно и непротиворечиво это излагать, вот тогда сразу же и... ;) Что, полагаю, из области чистой фантастики :)

PS. Ну и несколько ссылок на свои старые заметки на эту тему:
Мои крамольные мысли о будущем языков программирования
О предсказании сроков написания программ
Опыт в программировании: переход от решения задачи к ее формализации.

среда, 19 ноября 2014 г.

[prog.c++.wow] Малообразованные C++ненависники доставляют

Не могу не утащить этот феерический фрагмент к себе (источник):

STL контейнеры были придуманы для приближения C++ к высотам и требованиям развитых высокоуровневых ЯП типа java/python/lisp и пр, с сохранением возможностей баловаться на низком C-шном уровне. Как известно, одной и то же жо...ой невозможно усидеть на двух противоположных поездах. Поэтому в C++ то крест спадает, то трусы сами надеваются. И программист, пишущий на C++, только и занят тем, что пытается одновременно и крест на шее удержать и трусы снять. Зрелище не очень так.

Особенно доставляет тот факт, что Степанов STL для C++ начал делать в начале 90-х годов. Т.е. тогда, когда Java не было в дикой природе, а Python делал лишь первые робкие шажочки. Что до Lisp-а, то первоначально свои идеи Степанов обкатывал на Scheme (т.е. на Lisp-е), затем на Ada. Но единственным языком, на котором Степанов, по его же признанию, смог воплотить в жизнь свои идеи полностью, оказался именно C++.

Ну а после появления STL уже сам C++ начал подстраиваться под STL и этот процесс взаимного влияния языка и STL-я, к счастью, продолжается до сих пор.

PS. Процитированный мной персонаж, smeeld, походу, сейчас является знаковой фигурой на RSDN-е (с вполне понятным направлением знака). Многие точки над ё вокруг этого товарища расставила вот эта тема (например, этот ее фрагмент).

[prog.flame] Масло маслянное?

Написал вчера вот такую строчку:

static const threading threading = THREADING;

Почему-то вспомнился термин тавтология ;)

Это я местами пытаюсь следовать стилю именования классов из C++/Boost, где не принято давать суффиксы _t именам нешаблонных классов. Если же вернуться к моему старому, проверенному годами, стилю, то было бы вот так:

static const threading_t threading = THREADING;

Тоже не образец изящного стиля, но для меня чуть понятнее.

Собственно, пока в C++ не начал широко использоваться STL, использование нотации с суффиксом _t для имен типов не вызывало особых сложностей. Но вот когда STL, а затем и Boost, пошел в массы, возникают штуки, когда суффикс не то, чтобы мешает, но выставляет наружу слишком много информации. Ну, например, если суффикс _t используется:

std::sort(a.begin(), a.end(), my_predicate_t(b, c, d));

tools::adaptor_t< reader_t, multithreading_t::safe, file_logger_t > adaptor;

Глядя на такой код программист понимает, что при вызове std::sort в качестве предиката создается экземпляр типа my_predicate_t. А в параметрах шаблона adaptor_t используются имена типов reader_t, multithreading_t, file_logger_t.

С одной стороны это хорошо. Но с другой, при сопровождении кода, это знание может только мешать. Например, если my_predicate должен стать вызовом возвращающей хитрый объект функции. Или, если со временем multithreading_t перестанет быть типом, а станет, скажем, пространством имен. А reader_t из типа превратится в константу. Если бы в коде было изначально записано вот так:

std::sort(a.begin(), a.end(), my_predicate(b, c, d));

tools::adaptor_t< reader, multithreading::safe, file_logger > adaptor;

То при сопровождении вообще менять бы ничего не пришлось.

На такие вещи я периодически натыкаюсь. И поэтому мой старый стиль именования типов мне иногда не нравится. А для имен типов, которые могут быть предикатами или параметрами шаблонов, стараюсь использовать имена без суффикса _t. Что терпимо, но некоторой шизофренией отдает: здесь _t используется, а вот здесь не используется, а вот здесь не понятно :(

С другой стороны, от суффикса _t оказываться так же не хочется. Ибо с ним можно запросто писать вот так:

threading_t threading = threading_t::single;
if( is_multicore_environment() )
   threading = threading_t::multi;

Не доводя дело до "масла масляного":

threading threading = threading::single;
if( is_multicore_environment() )
   threading = threading::multi;

PS. Как бы мелко эта тема не выглядела, она, тем не менее, затрагивает одну из фундаментальнейших и сложно разрешимых проблем в программировании: выбор удачных названий для идентификаторов ;)

PPS. CamelCase -- это пройденный этап. Кому интересно, отправляю в историю: "О том, как я от CamelCase к lower_case пришел".

вторник, 18 ноября 2014 г.

[prog.c++] И этот человек еще что-то говорит против трехэтажных шаблонов?! ;)

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

templatetypename ENGINE >
class timer_manager_impl_template_t
   :  protected timer_manager_threading_dependent_part_t< ENGINE::threading > 
{
public :
   templatetypename... ARGS >
   timer_manager_impl_template_t( ARGS && ... args )
      :  m_engine( std::forward< ARGS >(args)... )
   {
   }
   ...
};

template<
   threading THREADING,
   typename ERROR_LOGGER,
   typename ACTOR_EXCEPTION_HANDLER >
class timer_list_manager_template_t
   : public details::timer_manager_impl_template_t<
               details::timer_list_engine_t<
                     THREADING,
                     ERROR_LOGGER,
                     ACTOR_EXCEPTION_HANDLER > > 
{
   ...
};

Что, почему-то, заставило вспомнить народное выражение "Чья бы корова мычала" :)))

PS. А вообще, конечно, C++11 рулит неимоверно. Жаль только, что какие-то фичи из С++11 в MS VisualStudio 2013 не поддерживаются. Например, наследование конструкторов. Но радует, что VisualStudio 2015 уже на подходе.