Показаны сообщения с ярлыком Из непонятого. Показать все сообщения
Показаны сообщения с ярлыком Из непонятого. Показать все сообщения

понедельник, 1 июня 2026 г.

[prog.c++] Попробовал познакомиться с модулями C++20 и чего-то недопонял

Провел пару простых экспериментов с модулями C++20 и получил странные результаты.

Эксперименты проводились под Windows с VS2022 и VS2026 (обновления от мая 2026-го) и ArchLinux с GCC 16.1 и clang 22.1.

Ожидаемые мной результаты (т.е. отсутствие проблем компиляции/линковки) получились только с clang 22.1 и libc++. А вот с GCC и VC++ случились какие-то проблемы, которые мне сложно объяснить.

Во всех случаях сборка осуществлялась через CMake и Ninja.

Исходные коды описанных ниже тестовых программ можно найти в этом репозитории.


Эксперимент первый (case_001 из упомянутого репозитория). Очень простой модуль. Декларация в файле hello.ixx:

понедельник, 9 марта 2026 г.

[prog.c++] Как будто бы недоделки в системе C++ных аллокаторов

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


Первая штука -- это отсутствие каких-то простых инструментов для создания нового объекта посредством аллокатора.

Если нам не нужно связываться с аллокаторами, то мы просто вызываем new T (или make_unique<T> или make_shared<T>) и все.

А вот в случае с аллокаторами сперва нужно вызвать у аллокатора allocate чтобы получить блок памяти под объект. А затем нужно вызывать у аллокатора construct, чтобы сконструировать объект в этом блоке. Т.е. два действия вместо одного. При этом, если construct бросает исключение, то нужно вручную освободить блок памяти посредством обращения к deallocate у аллокатора.

Но вот простого метода, который бы сперва сам вызвал allocate и следом construct, в стандартной библиотеке я не нашел. Есть там make_obj_using_allocator, но он вроде как для совсем другого.

Так же нет простого метода, который бы взял ссылку на аллокатор и указатель на удаляемый объект и сам бы сперва вызвал destroy для объекта, а потом deallocate для блока памяти. А хотелось бы иметь готовый, а не делать самостоятельно на коленке.

И да, я знаю, что allocate/construct и destroy/deallocate вызываются через allocator_traits, просто не упоминаю об этом для простоты изложения.


В C++17 в стандартную библиотеку добавили std::pmr::memory_resource. И, вроде как, имеет смысл делать свои арены в виде наследников от memory_resource.

Но, как я понял, дизайн memory_resource направлен на то, чтобы информировать о возникающих ошибках только через выброс исключения. Поскольку метод do_allocate должен бросать исключение при невозможности выделить память.

И тут вопрос: а как быть, если мы не хотим получать исключение? Например, нам хотелось бы иметь аналог new(std::nothrow). Типа попробовали выделить память, если не получилось, то у нас на руках нулевой указатель и мы можем попробовать обработать эту ситуацию без try..catch блока (ведь try..catch -- это дорого).

Мне кажется, что в memory_resource напрашивается метод allocate формата:

void *
allocate(std::nothrow_t,
  std::size_t bytes,
  std::size_t alignment = alignof(std::max_align_t));

и соответствующий ему метод do_allocate.

Но почему-то этого нет 🙁


У аллокатора есть свойство, которые выражаются вложенным типом propagate_on_container_copy_assignment. Если оно эквивалентно std::true_type, то аллокатор должен копироваться при копировании содержимого контейнера в операторе копирования.

При этом у аллокатора есть метод select_on_container_copy_construction, который должен вызываться у аллокатора в конструкторе копирования контейнера. Т.е. вот в этом случае:

some_container original{ ... };
...
some_container copy{ original }; // (1)

В точке (1) должен быть вызов original.get_allocator().select_on_container_copy_construction().

Что мне кажется странным и несколько недодуманным, так это то, что потенциально propagate_on_container_copy_assignment и select_on_container_copy_construction могут быть не согласованы.

Т.е. свойство propagate_on_container_copy_assignment может быть std::false_type (а по умолчанию это так и есть), при этом select_on_container_copy_construction может возвращать тот же самый аллокатор. Что приведет к тому, что в конструкторе копирования у нас будет копироваться аллокатор из контейнера-источника. А вот в операторе копирования мы аллокатор из источника копировать уже не будем.

А может быть и другой вариант: propagate_on_container_copy_assignment -- это std::true_type, тогда как select_on_container_copy_construction будет возвращать новый экземпляр аллокатора (не равный исходному). Тогда в конструкторе копирования мы будем получать новый экземпляр аллокатора, а в операторе копирования -- будем получать копию аллокатора из контейнера-источника.

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

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

понедельник, 2 марта 2026 г.

[prog.c++] Применимость идиом copy-then-swap и move-then-swap при наличии кастомных аллокаторов

В продолжение недавно начатой темы. Есть очень удобная идиома copy-then-swap, которая позволяет легко и просто написать для своего типа оператор копирования, обеспечивающий строгую гарантию безопасности исключений.

Для примера рассмотрим некий вымышленный тип, который содержит внутри пару векторов:

class special_container {
  struct description { ... };
  struct payload { ... };

  std::vector<description> m_descriptions;
  std::vector<payload> m_payloads;
...
};

И мы хотим, чтобы у special_container был оператор копирования со строгой гарантией безопасности исключений. Для этого нам потребуются:

  • обычный конструктор копирования;
  • не бросающий исключений swap.

что достигается весьма просто:

class special_container {
  ...
public:
  // Swap сделаем через свободную функцию.
  friend void swap(special_container & a, special_container & b) noexcept
  {
    using std::swap;
    swap(a.m_descriptions, b.m_descriptions);
    swap(a.m_payloads, b.m_payloads);
  }

  // Конструктор копирования.
  special_container(const special_container & other)
    : m_descriptions{ other.m_descriptions }
    , m_payloads{ other.m_payloads }
  {}
...
};

Имея в своем распоряжении эти базовые инструменты можно сделать и оператор копирования:

special_container &
special_container::operator=(const special_container & other)
{
  special_container tmp{ other };
  swap(*this, tmp);
  return *this;
}

Фокус здесь в том, что возможные исключения вылетят при формировании объекта tmp. Но при этом ничего не меняется в this. А если при конструировании tmp исключений не случилось, то мы заменяем содержимое this содержимым tmp.

Еще один приятный фокус в том, что такая примитивная реализация прекрасно защищает и от присваивания самому себе. Впрочем, если экземпляры special_container "тяжелые", а вероятности самоприсваивания не нулевая, то можно и по старинке:

special_container &
special_container::operator=(const special_container & other)
{
  if(this != std::addressof(other))
  {
    special_container tmp{ other };
    swap(*this, tmp);
  }
  return *this;
}

Пока что все идет замечательно.

Но давайте представим себе, что нам потребовалось научить special_container работать с разными аллокаторами. Т.е. тип special_container превращается во что-то вроде:

template<typename Alloc>
class special_container
{
  struct description {};
  struct payload {};

  using alloc_traits = std::allocator_traits<Alloc>;
  using description_allocator = alloc_traits::template rebind_alloc<description>;
  using payload_allocator = alloc_traits::template rebind_alloc<payload>;

  std::vector<description, description_allocator> m_descriptions;
  std::vector<payload, payload_allocator> m_payloads;
...
};

Сможем ли мы и дальше пользоваться идиомой copy-then-swap?

И вот тут у меня есть сомнения. А в попытках разобраться как раз и получился этот пост.

У аллокатора может быть такое свойство как propagate_on_container_swap. Если это свойство выставлено в std::true_type, то при выполнении swap мы можем обменять аллокаторы для контейнеров.

Грубо говоря, допустим, что у нас есть собственный тип аллокатора:

понедельник, 26 января 2026 г.

[prog.thoughts] Как ИИ может отбирать хлеб у разработчиков библиотек

Сразу хочу жирный disclaimer: я не утверждаю, что сказанное мной ниже уже является реальностью. Но есть ощущение, что к этому идет. Буду только рад, если в итоге ошибусь.


Еще десять лет назад у разработчиков OpenSource продуктов была такая опция для монетизации своей работы как платные консультации и платное обучение. Грубо говоря, есть открытая библиотека X за которой стоит компания Y. И если вы не можете разобраться с X самостоятельно, то обращаетесь к Y за помощью, а Y направляет к вам людей, которые учат вас, отвечают на ваши вопросы и подсказывают вам какие-то решения, которые вы не видите в силу своего незнания X.

Сейчас же большинство вопросов по любой OpenSource-библиотеке можно решить посредством того или иного ИИ-инструмента. Соответственно, нет надобности обращаться за помощью к разработчиками. А раз так, то и платные консультации/обучения отмирают за ненадобностью.

Грубо говоря, если 10 лет назад я рассчитывал на то, что вокруг OpenSource можно зарабатывать на трех вещах:

  • платная поддержка и оперативное устранение проблем для тех клиентов, которые готовы за это платить;
  • кастомизация под нужды конкретного клиента, возможно даже с созданием закрытого форка;
  • платные консультации и обучение.

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


Чтобы затронуть еще один важный момент нужно ответить на вопрос, а зачем вообще нужны библиотеки?

На мой взгляд, библиотеки упрощают решение конкретных задач. Например, библиотека для работы с файловой системой позволяет нам получать списки файлов, удалять и переименовывать файлы, устанавливать текущий каталог и т.д. Библиотека для файлового ввода/вывода позволяет нам читать и записывать содержимое файлов. Библиотека для работы с форматом JSON позволяет нам получать данные из JSON-представления или преобразовывать данные в JSON-представление. И т.д., и т.п.

Почему библиотеки возникли?

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

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

Однако, времена изменились. ИИ-евангелисты предсказывают, что человек будет писать промпты, а не код. Код же будет генерироваться ИИ-шками из промптов. И что там будет творится в этом сгенерированном коде уже не важно (по заверениям тех же ИИ-евангелистов).

Возможно это приведет к тому, что библиотеки отживут свое как явление.

Действительно, зачем нужна удобная библиотека для парсинга JSON-а, если ИИ по спецификации может сгенерировать фрагмент, который будет разбирать входной поток байт прямо "по месту"? Кого вообще будет заботить, что ИИ сгенерировал "рукопашный" разбор вместо того, чтобы использовать готовую JSON-библиотеку? Особенно, если сгенерированный код справляется со своими задачами и покрыт тестами (сгенерированными тем же ИИ).

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

И чем лучше и удобнее были те самые библиотеки, тем проще было прикладным программистам.

Но сейчас результат вполне можно будет получать и без всех этих промежуточных инструментов. Т.е. опираясь только на API операционной системы и средства стандартной библиотеки языка программирования ИИ может генерировать все, что нужно: от парсинга аргументов командной строки до координации распределенных транзакций.

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

вторник, 11 ноября 2025 г.

[prog.c++] А зачем в современных условиях нужен обновляемый раз в три года стандарт C++?

Вернуться к теме трехлетнего периода обновления стандарта C++ заставил свежий отчет Герба Саттера о недавнем заседании комитета по стандартизации C++. В частности одна фраза оттуда:

For trivial relocatability, we found a showstopper bug that the group decided could not be fixed in time for C++26, so the strong consensus was to remove this feature from C++26.

Т.е. в одной из фич C++26 обнаружили проблему, исправить эту проблему до принятия C++26 не успевают, поэтому фича из стандарта была выброшена вообще. И, в лучшем случае, она станет частью C++ только в 2029-ом, в рамках C++29.

Что приводит к вопросу о том, а хорошо ли, что C++ обновляется всего раз в три года?

Почему бы не принимать обновленные редакции два раза в год?

Скажем, C++26.1 в марте 2026, а C++26.2 в сентябре 2026. Потом C++27.1 в марте 2027, а C++27.2 в сентябре 2027. И т.д.

Когда конкретное предложение (например, по trivial relocatability) будет готово, тогда оно и попадет в ближайшую редакцию. Не успеет к C++26.1 -- попадет в C++26.2, не успеет к C++26.2 -- значит попадет в C++27.1.

Я это к тому, что этикетка, скажем, C++23, в современных обстоятельствах ничего не значит. Есть на бумаге стандарт C++23, а на практике от этого может не быть никакого толку. Даже если вы сидите на самых свежих компиляторах, все равно в них может не быть полной поддержки C++23. Поэтому отталкиваться приходится не от стандарта, а от версии компилятора. А раз так, то какая разница, не поддерживает компилятор обновляющийся раз в три года стандарт, или же он не поддерживает выходящую два раза в год спецификацию? Как по мне, то никакой.

Так уж получается, что в конце 2025-го года совершенно все равно, является ли std::start_lifetime_as частью C++23 или же частью C++20, или же частью условного C++27.2. Нет start_lifetime_as в нужных мне компиляторах и непонятно когда появится, так что без разницы, приняли ли start_lifetime_as в C++20 или C++23.

Стандарт C++ давным-давно превратился в нечто вроде "заявления о намерениях", которые когда-то как-то может быть будут реализованы. И какой смысл выпускать это "заявление о намерениях" раз в три года? Точно так же можно делать это и два раза в год. В начале 2026-го что-то добавили в C++, затем в конце 2026, затем в начале 2027-го. Все равно затем ждать пару-тройку лет.

Можно предположить, что сама по себе процедура стандартизации C++ не рассчитана на выпуск двух стандартов в год. Типа там много накладных расходов на бюрократию и вот это вот всё.

Однако, можно задать следующий напрашивающийся вопрос: а зачем в современных условиях вообще нужен стандарт C++ именно как ISO-стандарт?

Почему роль этого самого "заявления о намерениях" не может играть некая спецификация, публикуемая от имени некоторой некоммерческой организации? Зачем сейчас развивать C++ под эгидой ISO?

Можно понять значимость стандартизации в начале 1990-х, когда было с полдюжины живых C++ных компиляторов от совершенно разных поставщиков. Некоторые из которых (Borland и Microsoft, как минимум) еще и норовили свои расширения в язык добавлять. В таких условиях роль толстого фолианта с печатью ISO стандарта была понятна.

Но сейчас-то? На фоне того, как постепенно удушающие С++ в смертельных объятиях Java, C#, Go и Rust, вообще не парятся какой-либо стандартизацией. Просто развиваются и просто делают C++ все менее и менее нужным.

В общем, иногда мне кажется, что главным врагом C++ становится сам комитет по стандартизации и его приверженность странным (мягко говорям) правилам работы.

воскресенье, 19 октября 2025 г.

[prog.c++] Прекрасное от автора функции на 700 строк...

Некоторое время назад в блоге был пост Наткнулся на образчик кода из категории "Да не дай боже такое сопровождать!". Я там прошелся по качеству кода от одного из RSDN-овских форумчан. Код, как по мне, из категории "лучше бы такого никогда не видеть, тем более не сопровождать".

А на днях этот же форумчанин открыл на RSDN-е новую тему, от которой я испытал что-то вроде шока. Почему шока постараюсь объяснить ниже (хотя не уверен, что это получится).

Итак, суть в том, что у человека образовалась цепочка из if-else в количестве 200 (двухсот(!!!)) штук. Из-за чего VC++ отказался компилировать данную развесистую конструкцию с ошибкой "MSVC C1061: compiler limit: blocks nested too deeply". И автор топика спрашивает у читателей что можно сделать в такой ситуации.

В одном из своих ответов в начатом обсуждении автор темы привел пример того, с чем он имеет дело:

else if ( opt.setParam("VAR:VAL")
      || opt.isOption("set-var") || opt.isOption("set-condition-var") || opt.isOption('C')
      || opt.setDescription("Set variable valie for conditions and substitutions"))
{
   if (argsParser.hasHelpOption) return 0;

   if (!opt.hasArg())
   {
       LOG_ERR<<"Setting condition variable requires argument (--set-condition-var)\n";
       return -1;
   }

   auto optArg = opt.optArg;
   if (!appConfig.addConditionVar(optArg))
   {
       LOG_ERR<<"Setting condition variable failed, invalid argument: '" << optArg << "' (--set-condition-var)\n";
       return -1;
   }

   return 0;
}

При этом автор заявляет буквально следующее:

Обработка ключей командной строки.

На какой-нибудь map с хэндлерами не переделать, потому что у меня в режиме --help пробегается по этим условиям и собирает выдаваемую потом информацию. Тут в if сразу и длинный ключ set-var задаётся, и короткий C, и подсказка по формату значения VAR:VAL и описание опции Set variable valie for conditions and substitutions — в одном месте всё задаётся

Это, собственно, и подвигло меня написать данный пост. Т.е. сперва я был шокирован самим фактом появления данной темы на RSDN: это же сделал профессиональный программист, которому деньги платят за написание кода. Написание нормального кода. Т.е. задачей программиста изначально является то, чтобы такого говна в коде не было. Вот просто от слова совсем. И если ты этого обеспечить не можешь, то может нужно профессию сменить? В менеджмент уйти, к пример. Ну или подучиться немного этой самой профессии...

Но вишенкой на торте стало это самое "На какой-нибудь map с хэндлерами не переделать". Тут уж мое терпение лопнуло и решился таки пожертвовать частью своего выходного дня, чтобы написать данный текст.

Я даже не буду затрагивать тему того, что в продвинутых инструментах для работы с аргументами командной строки многие проблемы уже решены "by design". Например, можно посмотреть на Lyra или args, или даже boost::program_options. Мало ли какие условия заставили человека использовать что-то специфическое (хотя, подозреваю, что им же и написанное). Поэтому сконцентрируемся только на длинной цепочке if-else.

В общем, поскольку в нашем распоряжении только небольшой кусочек демонстрационного кода, то будем отталкиваться именно от него. Из-за чего предлагаемые ниже решения, скорее всего, не подойдут на 100% автору RSDN-новского топика, т.к. наверняка есть какие-то неозвученные на RSDN-е моменты. Но и нет цели показать 100% решение, смысл в том, чтобы показать тот вариант, который лично мне сразу же пришел в голову. И от которого можно оттолкнуться, чтобы избежать длинной портянки из if-else.

Суть в том, что с каждым if-ом у нас есть два связанных блока кода. Первый отвечает за описание одного аргумента командной строки (он помещается внутри условия в if-е). Второй отвечает за обработку аргумента (он помещается внутри then-ветки для if-а).

А раз так, то пусть каждый из блоков представляется в виде std::function. И оба эти блока пусть объединяются в рамках одной структуры:

понедельник, 23 июня 2025 г.

[prog.c++] Есть ли теперь смысл при разработке C++ библиотек придерживаться не самых свежих стандартов?

В заголовок поста вынесен вопрос, который занимает меня в последние пару-тройку лет. A последний год так точно.

Поясню в чем дело.

В C++ на протяжении десятков лет есть специфическая картина: существует официальный С++ на "бумаге", т.е. описанный в соответствующем стандарте, будь то С++98, С++11 или C++23, и есть реальный C++, доступный конкретному разработчику в конкретном компиляторе. И, как правило, в имеющихся в нашем распоряжении компиляторах далеко не все фичи из самого последнего официального стандарта реализованы.

Эта картина особенно важна при разработке кросс-платформенных библиотек. Если ты хочешь, чтобы твою библиотеку использовали разные люди в разных проектах и на разных платформах, то ты вынужден занижать версию стандарта. Например, вместо C++23 (который вроде как уже два года нам доступен) делать библиотеку на C++17 или даже C++14.

При разработке софта для конечного пользователя ситуация, зачастую, гораздо проще: очень часто софт пишется под конкретную платформу и вполне можно заложиться на конкретный компилятор. Но с разработкой библиотек не так. В проекте X библиотека может работать уже в режиме C++23, тогда как в проекте Y -- все еще в C++17.

До недавнего времени я воспринимал такую ситуацию как данность. Типа в разработке прикладного софта для конечного пользователя своя специфика, а при разработке библиотек -- своя. Нужно смириться и получать удовольствие.

Но после C++20 смиряться все сложнее.

C++11 стал совсем другим языком в сравнении с C++98. Два следующих стандарта, C++14 и С++17, инкрементально улучшали C++, но не могу сказать, что они переводили язык на какой-то принципиально другой уровень (даже не смотря на такие крутые фичи C++17 как structured binding и if constexpr). А вот начиная с C++20 все принципиально поменялось:

  • C++20 добавил концепты и operator spaceship (про модули промолчу ибо не пробовал);
  • С++23 добавил deducing this. Не смотря на то, что (на мой взгляд) сделали это через одно место (и можно было бы по-другому), но таки важную реальную проблему эта фича решает;
  • С++26 добавляет compile-time рефексию и, я очень надеюсь, контракты.

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

И вот глядя на все это великолепие в свежих стандартах С++ я не могу найти для самого себя ответ на вопрос: а зачем на при разработке своих библиотек оставаться на C++17/14/11?

Вот реально.

Ладно бы нам за наши OpenSource проекты платили. Но этого нет. И RESTinio, и SObjectizer приносят нам деньги не напрямую, а приводя к нам клиентов через репутацию. Зачастую новые фичи в тот же SObjectizer добавляются "just for fun" -- просто что-то выглядит интересным или представляет из себя вызов, поэтому делаешь это ради получения удовольствия.

И если уж удовольствие от разработки выходит на передний план, то зачем лишаться концептов, контрактов или рефлексии? Ради чего?

Поэтому чем дальше, тем больше мыслей о том, чтобы в следующем году перевести SObjectizer сразу на C++26. На счет RESTinio ситуация посложнее, но если представиться возможность плотно поработать над RESTinio, то и там тоже можно будет сразу же брать С++26.

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

пятница, 18 октября 2024 г.

[prog.flame] Адекватен ли выбор языка программирования для такой задачи (Rust для Radicle)?

Часто говорят, что язык программирования нужно подбирать под задачу. Мол, не нужно брать C++ для того, что легко делается на Python. И не нужно брать Python там, где потребуется что-то более быстрое и менее ресурсоемкое.

Недавно узнал о Radicle -- это что-то вроде GitLab и Gitea, т.е. инструмент для совместной работы над программным кодом.

Radicle написан на Rust-е.

Не на Python-е. Не на Ruby. Не на Node.js. Не на Java или C#. Не на Go. А на Rust-е.

Как по мне, так это почти тоже самое, что и GitLab, написанный на C++. С поправкой на то, что, во-первых, Rust -- это можно и молодежно. И, во-вторых, для программирования Web-приложений все-таки Rust побезопаснее C++ будет. Но, в общем-то, тоже самое, вид в профиль.

Посему лично я в недоумении и сам бы для проекта, подобного Radicle, точно бы не стал брать C++ или Rust. Как по мне, так здесь достаточно языка со сборкой мусора: Java, Kotlin, C#, возможно даже Go (простите, динамика в лице Python/Ruby/JavaScript идет лесом, т.к. не из тех мазохистов, которые делают что-то большое на динамически-типизированных языках).

А вот брать нативный язык без GC с претензией на околосистемность... Зачем?

Собственно, непонимание и привело к появлению этого поста. Надеюсь, в комментариях мне напихают в панамку и объяснят насколько я дебил и отстал от современных трендов. Ибо я реально не понимаю, почему в 2020-х нужно отказываться от всего, что накопилось в мире безопасных языков с GC и трахать себе мозг Rust-ом в задаче, где не нужна ни супер-пупер производительность, ни супер-пупер экономия ресурсов, ни тотальный контроль за происходящим.


Если не лень, то напишите в комментариях, плиз, какой бы вы язык программирования предпочли для реализации проекта по типу Radicle/GitLab/Gitea. Я бы лично выбирал бы между C#, Kotlin и Go (хотя не на одном из них не программировал), но точно бы не C++ и не Rust.

четверг, 1 августа 2024 г.

[prog.c++.wtf] Еще немного про самый странный паттерн в коде

Продолжение вот этой темы. За прошедшие месяцы регулярно сталкивался с этим паттерном в разных кодовых базах, написанных разными разработчиками. Такое ощущение, что выросло новое поколение программистов, смотрящих на код совсем по-другому.

Вроде бы понял, в чем смысл. С обычными return-ами часто пишут так:

int some_func(some_arg & arg) {
  if(!is_valid(arg))
    return -1;

  if(!approriate_state(arg))
    return -1;

  if(!some_another_condition(arg))
    return -1;

  ... // Ну а здесь уже какие-то действия.
}

Похоже, этот подход пытаются переложить на тело цикла, только вместо преждевременных return-ов используют continue:

for(auto & item : collection) {
  if(!is_valid(item))
    continue;

  if(!appropriate_state(arg))
    continue;

  if(!some_another_condition(arg))
    continue;

  ... // Ну а здесь уже какие-то действия.
}

Понять-то я понял, а вот принять не получается 🙁

Во-первых, дело в том, что в реальном коде все это выглядит не так опрятно и понятно, как в моих псевдопримерах. Между if-ами, зачастую, еще какие-то действия выполняются, а сами if-ы могут быть и вложенными, и кроме continue еще встречаются и break, и даже return...

В общем, когда пытаешься вчитаться в незнакомый код вот с такими вот continue, то приходится в голове таблицу переходов вручную выстраивать. Невольно вспоминаются времена программирования на Basic-е с нумерацией строк и ассемблере 😁

Во-вторых, недавно довелось несколько циклов преобразовать в лямбды, которые передаются во что-то типа `std::ranges::for_each`, т.е. было:

for(const auto & item : detect_collection_for_processing()) {
  ... // Тут обработка с continue/break-ами.
}

а стало:

for_all_ready_to_processing_items(token, [&](const auto & item) {
  ... // Тут обработка, но уже без continue/break.
});

Так вот, оказалось, что тела тех циклов, которые были написаны по-человечески, без continue и break, трансформируются в лямбды легко и непринужденно. Ну вот вообще не припомню, чтобы нужно было делать что-то кроме замены for-а на вызов функции с лямбдой.

Тогда как тела циклов с continue -- это вот прям минное поле 😔
Хорошо хоть, что компилятор бьет по рукам когда видит continue вне цикла. Несколько раз это меня выручало, т.к. в сложных if-ах я continue просто не замечал.

Посему в очередной раз приходится делать вывод о том, что куски кода, оформленные в виде "одна точка входа, одна точка выхода", оказываются наиболее простыми и в изучении, и в сопровождении. Деды, которые учили меня структурному программированию 35 лет назад, были таки правы.


Не хочу создать впечатление, что в моем коде только правило "единственного return-а" и ничего больше.

Это далеко не так.

И break-и использую, и несколько return-ов из функций. В том числе, бывает, сочетаю в теле цикла и break, и return.

Но стараюсь, чтобы таких вещей было по минимуму. Ибо недостаточно умен, чтобы писать и поддерживать сложный код. Так что тут, как говорится, не было счастья (т.е. ума), да несчастье (т.е. низкий IQ) помогло.

А вот continue в моем коде, действительно, найти тяжело. Практически не использую. Чего и вам желаю 😎

суббота, 20 июля 2024 г.

[life.business] Чего только в жизни не бывает...

Увидел некоторое время назад в ленте LinkedIn:

До сих пор пребываю в растерянности.

С одной стороны, очевидно, что 30 лет своей профессиональной деятельности занимался какой-то фигней. Мягко говоря.

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

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

суббота, 11 мая 2024 г.

[prog.c++] Из непонятого: как же лучше погружать в современный C++ новых людей?

У меня недавно закончился небольшой контракт на проведение для молодых программистов чего-то вроде "курса молодого бойца" по C++. Что заставило в очередной раз задуматься на тему "а не стал ли C++ настольно монстрообразным, что обучить новичка практически невозможно?"

Но это сильно глобальный вопрос, я даже не знаю как к нему подойти. Радует лишь то, что сам я C++ изучаю года с 1991-го, когда это был, мягко говоря, совсем другой язык. Гораздо более простой, последовательный и логичный. И мне повезло, что на эту небольшую базу затем инкрементально ложились новые возможности: пространства имен, исключения, шаблоны, STL и т.д., и т.п. Т.е. сам я учил C++ по мере того, как в него добавлялись новые фичи. И поскольку добавлялись они раньше не в таком количестве и не с такой скоростью, то поспевать за ростом сложности C++ где-то до 17-го стандарта еще удавалось. Однако, на C++20 это уже сломалось даже для меня :(

Впрочем, это опять таки уход в сторону от того, о чем хотелось сегодня поговорить.

Попробую сконцентрироваться на более простом вопросе, ответ на который для меня самого совсем неочевиден.

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

Как по мне, Си и C++ уже очень и очень давно совершенно разные языки. Да, в C++ есть изрядное подмножество чистого Си, но все-таки идеология C++ другая и к таким вещам, как ссылки, классы, деструкторы, нужно привыкать сразу. Чем меньше приемов из чистого Си, тем лучше.

Поэтому вроде как есть смысл новичков сразу учить использовать то хорошее, что есть в C++. Например, std::string для строк вместо char*, std::string_view вместо const char*, std::vector и std::array вместо Си-шных массивов, std::find_if вместо голых циклов и вот это вот все.

Но тут есть серьезная засада, особенно в случаях, когда у новичков за плечами уже есть опыт работы с языками со сборкой мусора (будь то Python или Java, не суть важно). По моим впечатлениям у тех, кто изучает C++ сейчас, есть проблемы с пониманием разницы между values и references. Грубо говоря, люди пишут:

void f(std::string param) {...}

даже не задумываясь о том, что здесь происходит передача по значению, а не по ссылке.

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

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

Рефлексируя на эту тему я пока придерживаюсь мнения, что виной тому как раз использование готовых "высокоуровневых" возможностей STL. Те же самые std::string и std::vector освобождают программиста от изрядного геморроя, но не дают этому самому программисту понимания того, как же это все работает "под капотом". А без такого понимания сложно программировать на C++ эффективно.

Что заставляет задуматься о том, чтобы учить людей C++у без использования готовых классов и шаблонов из STL.

Грубо говоря, если начинать обучение с того, чтобы молодой C++ник сам написал аналог string-а, vector-а и hash_map-а, сам бы почувствовал где и когда вызываются конструкторы, деструкторы, операторы копирования, где требуется выделить память, где освободить, где скопировать содержимое и столкнуться с проблемой exception safety... Вот если начинать обучение с этого, то может быть у человека будет гораздо больше понимания разницы между передачей по значению или по ссылке, между копированием и перемещением и т.д., и т.п.

Такой подход, на первый взгляд, производит впечатление более перспективного. Мол, мы сперва даем людям базу. А потом на эту базу кладем знания об уже готовых классах/алгоритмах из STL, чтобы можно было ощутить насколько жизнь может быть проще если пользоваться тем, что уже написано.

Но и здесь все не так радужно. Во-первых, такое обучение потребует гораздо больше времени. Ведь если мы научим молодого C++ника делать свой vector, то он все равно еще будет далек от того, чтобы уметь писать нормальный прикладной код.

Во-вторых, по моему субъективному мнению, написание библиотек классов (пусть даже в такой библиотеке будет всего лишь string, vector и hash_map) требует несколько иных навыков, нежели написание прикладного кода. Да, возможно это тараканы в моей голове, но я убежден, что библиотеко-писатели и прикладники -- это разные специалисты. Бывают, конечно, и те, кто одинаково хорош и в том, и в другом, но это такое же частое явление, как и хорошие программисты, вырастающие в хороших менеджеров. В общем, начиная с реализации собственных string и vector можно не получить должного эффекта, если обучаемый больше склонен к решению прикладных проблем, чем к написанию библиотек.

В-третьих, этот подход как раз таки очень близок к подходу, когда мы сперва учим людей чистому Си, а уже затем переходим к использованию высокоуровневых возможностей C++. Близок, не не тождественен. Тем не менее, если молодой разработчик привыкает писать относительно низкоуровневый код с ручными new/delete, alignof/alignas и пр. заморочками, то есть опасность, что он и продолжит в том же духе. Тут людей сразу пытаешься учить пользоваться std::unique_ptr и std::make_unique, а они все равно норовят использовать голые владеющие указатели с new/delete... Что уж говорить о тех, кого специально с самого начала ограждают от std::unique_ptr, std::optional, std::variant, std::vector и т.д.

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

В завершение еще раз поделюсь своими тягостными впечатлениями о том, что C++ на мой взгляд, стал слишком уж большим и сложным для того, чтобы его мог изучить и освоить человек "со стороны". Речь не о старпёрах, вроде меня, кто за 30 лет в программизме имел возможность осваивать фичи "современного C++" по мере их появления. А о тех, кто про C++ ничего не знал, но должен изучить за относительно небольшое время. Как таким новичкам преподавать тот же С++20? Вот это вопрос.

Хотя, может быть я излишне пессимистичен. Может это лично мне тяжело осваивать C++20, т.к. я уже старый, мозги уже плесневеть начали, много там лишних и устаревших знаний скопилось, да еще и не так уж много возможностей применять C++20 на практике. Отсюда и мое собственное заблуждение о том, что изучать C++20 сложно. А когда человеку 20 лет, все вокруг неизвестно и удивительно, то может быть C++20 залетает в пустую юную голову просто "на ура"... Ведь может же быть и такое.

понедельник, 15 апреля 2024 г.

[prog.c++.wtf] Один из самых странных паттернов в коде, с которым доводилось сталкиваться...

В течении последнего года, может быть чуть меньше, регулярно стал натыкаться в разных кодовых базах на паттерны вроде вот такого (внимание на тело цикла for):

bool does_contain_apropriate_item(
   const item_container & items,
   const search_criteria & search_params)
{
   for(const auto & i : items) {
      if(!does_meet_coditions(i, search_params)) {
         continue;
      }

      return true;
   }

   return false;
}

Зачем нужен continue в цикле и почему нельзя сразу написать:

bool does_contain_apropriate_item(
   const item_container & items,
   const search_criteria & search_params)
{
   for(const auto & i : items) {
      if(does_meet_coditions(i, search_params))
         return true;
   }

   return false;
}

Большая и неразрешимая для меня загадка.

Возможно, выросло поколение, которое лояльно относится к break/continue в циклах. А может уже и не одно поколение.

PS. Почему не используется в таких случаях std::find_if -- это отдельный вопрос. Местами не такой простой, как может показаться. Тем более, что я привел не реальный фрагмент кода, а общую схему того, что вижу в последние месяцы регулярно. Детали же часто сильно отличаются, иногда еще нужен и индекс найденного элемента, так что на практике std::find_if не всегда такая уж удобная замена.

среда, 29 ноября 2023 г.

[prog.c++.flame] Насколько эффективно реализована split_path в C++ REST SDK от Microsoft?

Для многих анонимных экспертов, громогласно и авторитетно заявляющих о том, что C++у не место в современном мире, может быть удивительно, но C++ применяется в Web-е не так уж и редко. Может и не напрямую в Web-е, но вот для отдачи JSON-ов и прочей дряни в RESTful-микросервисной архитектуре C++ используется регулярно. И C++ных фреймворков для этих целей, разной степени продвинутости, можно сходу назвать с полдюжины (а если подумать-повспоминать, то и еще больше).

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

Но, полагаю, одна из причин -- это эффективность.

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

Ну а раз эффективность -- это одна из причин использовать C++, то, полагаю, C++ные фреймворки для поддержки HTTP/REST, должны бы подходить к этому аспекту с тщанием, ведь так?

Ага, я тоже так думал.

Пока не довелось заглянуть в потроха C++ REST SDK от самого Microsoft.

понедельник, 20 ноября 2023 г.

[prog.oop] Что-то я не понял пассаж Барбары Лисков из её статьи от 1987-го года...

Сама статья "Data Abstraction and Hierarchy (OOPSLA ‘87 Addendum to the Proceedings)", раздел "6.2. Multiple Implementations" (стр.16):

It is often useful to have multiple implementations of the same type. For example, for some matrices we use a sparse representation and for others a nonsparse representation. Furthermore, it is sometimes desirable to use objects of the same type but different representations within the same program.

Object-oriented languages appear to allow users to simulate multiple implementations with inheritance. Each implementation would be a subclass of another class that implements the type. This latter class would probably be virtual; for example, there would be a virtual class implementing matrices, and subclasses implementing sparse and nonsparse matrices.

Using inheritance in this way allows us to have several implementations of the same type in use within the same program, but it interferes with type hierarchy. For example, suppose we invent a subtype of matrices called extended-matrices. We would like to implement extended-matrices with a class that inherits from matrices rather than from a particular implementation of matrices, since this would allow us to combine it with either matrix implementation. This is not possible, however. Instead, the extended-matrix class must explicitly state in its program text that it is a subclass of sparse or nonsparse matrices.

The problem arises because inheritance is being used for two different things: to implement a type and to indicate that one type is a subtype of another. These uses should be kept separate. Then we could have what we really want: two types (matrix and extended-matrix), one a subtype of the other, each having several implementations, and the ability to combine the implementations of the subtype with those of the supertype in various ways.

Что в моем вольном переводе на русском языке звучит так:

Часто бывает необходимо иметь несколько реализаций одного типа. Например, для некоторых матриц мы используем разреженное представление, а для других обычное. Более того, иногда бывает нужно использовать в программе объекты одного типа, но с разными реализациями.

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

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

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

Так вот, я не понимаю почему Барбара Лисков сделала утверждение, что в рамках ООЯ мы не можем отнаследовать "расширенную матрицу" просто от класса "матрица" забив на существование подклассов "разреженная матрица" и "обычная матрицы", которые также наследуются от класса "матрица".

Очень смущает, что статья от 1987-го года, и там в списке литературы нет ни одной ссылки на описание таких языков программирования, как Eiffel (1986) и C++ (1985). Есть упоминание разве что SmallTalk, но в SmallTalk, насколько я помню, вообще не было понятия "абстрактный базовый тип" (или "интерфейс" в Java-подобных языках). Тогда как и в Eiffel, и в C++ понятие "абстрактного (базового) типа" как раз есть и в Eiffel/C++ мы запросто можем решить озвученную проблему имея просто абстрактный тип matrix и отнаследованный от него абстрактный тип extened_matrix.

И, как мне кажется, появление в Eiffel/C++ (а потом и в их наследниках) понятия "абстрактный (базовый) тип" как раз и позволило получить то самое разделение, о котором и говорила Барбара: отношение наследования абстрактных базовых типов как раз определяет отношение "тип является подтипом другого", тогда как наследник, реализующий абстрактный базовый тип, предоставляет реализацию. И таких реализаций может быть несколько. И они могут комбинироваться в произвольных вариациях до тех пор, пока мы манипулируем именно базовыми абстрактными типами.

В общем, требуется пояснительная бригада. Желательно с ссылками на публикации, в которых этот тезис Барбары Лисков разбирается (может быть даже самой Барбарой в последующих работах).

четверг, 13 июля 2023 г.

[prog.c++] И вот ради этого C++ становится все сложнее и сложнее?

Простите, но у меня бомбануло. Только что узнал, что в C++20 стало можно писать вот так:

std::vector pointsScored {27, 41, 32, 23, 28};

for (typedef int Points; Points points : pointsScored) {
    std::cout << "Jokic scored " << points << '\n';
}

А в C++23 исправили просчет, допущенный в C++20: разрешили кроме typedef-а в подобных конструкциях применять using:

std::vector pointsScored {27, 41, 32, 23, 28};

for (using Points = int; Points points : pointsScored) {
    std::cout << "Jokic scored " << points << '\n';
}

Хвала всевышнему, уж теперь-то заживем! (Это горький сарказм, если что)

Да уж. Написать по старинке, т.е.:

std::vector pointsScored {27, 41, 32, 23, 28};

{
    using Points = int;
    for (Points points : pointsScored) {
        std::cout << "Jokic scored " << points << '\n';
    }
}

было не судьба, нужно было язык усложнять еще больше.

Верной дорогой, блин... :(

вторник, 11 июля 2023 г.

[prog.c++] Ну как так то?

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

Началось все с того, что разыскивая информацию про NAT/STUN/UPnP/port-forwarding и иже с ними, наткнулся на проект pjsip. Был приятно удивлен тому, насколько приятно он оформлен в плане количества документации. Не берусь судить о ее качестве, т.к. смотрел сильно мельком и по диагонали. Но первое впечатление: внушаить!

Под этим положительным впечатлением решил заглянуть в исходники. Ну, думаю, аккуратно написанный на чистом Си проект, надо же поучиться тому, как должен выглядеть хороший Сишный код. Заглянул в первый попавшийся файл:

пятница, 20 января 2023 г.

[prog.flame] И долго в Boost будут тащить все, что не попадя?

На включение в Boost претендует библиотека Aedis, ревью идет прямо сейчас. Aedis -- это написанный на Asio клиент для Redis-а.

Определенно нужная для кого-то вещь, спору нет. Автору библиотеки респект и уважуха на полном серьезе. Так что у меня нет никаких сомнений о нужности Aedis-а вообще.

Зато есть вопрос "А что сейчас есть Boost и зачем он нужен?"

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

Но клиент для Redis-а?

Его что, кто-то хочет видеть в стандартной библиотеке C++? Серьезно?

А раз нет, то какой смысл развивать Boost как сборище всякого разного и разнообразного? Зачем весь этот винегрет держать под одной крышей?

Простите мне мой цинизм, но я вижу в этом всего лишь одну цель: паразитирование на известности Boost-а.

Вот жила-была себе библиотека, мало кто про нее знал. А тут она раз, и в Boost-е. И, поскольку множество C++ников выросло на принципе "если что-то нужно, то посмотри сперва в Boost", то как раз такие C++ники посмотрят сперва в Boost, возьмут оттуда первую попавшуюся и не будут больше ничего искать.

Boost уже сейчас скопище из более сотни (если не ошибаюсь) библиотек. Причем даже с некоторым дублированием (сколько там сейчас библиотек для работы с конечными автоматами? сколько для парсинга?). Сам по себе вопрос "А вы знаете Boost?", который был актуальным году в 2005-ом, сейчас уже потерял смысл. А человека, который ответит на него "Да" мне будет сложно воспринимать всерьез.

Может быть включение в Boost гарантирует то, что авторов библиотек будут финансировать? Или есть гарантия, что библиотеку автоматически подхватят, когда первоначальный автор перестанет ей заниматься?

И ладно бы Boost задавал какую-то недостижимую планку качества, а библиотеки оттуда не имели бы никаких вменяемых альтернатив за пределами Boost-а.

Так ведь нет.

Какие-нибудь Catch2 или doctest могут быть намного более простыми и удобными альтернативами Boost.Test. А spdlog может быть удобнее и практичнее, чем Boost.Logging. А fmtlib чем Boost.Format.

Т.е. жизнь показала, что и вне Boost-а можно создавать отличного качества библиотеки, которые будут востребованы больше, чем аналоги из Boost-а.

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

Но нет, Boost продолжает вбирать в себя всякую всячину.


PS. Из личного: убежден, что если бы Beast не протолкнули бы в Boost, то ее популярность в C++ мире была бы сильно ниже. Впрочем, если кто-то взял в проект Boost.Beast просто потому, что "это же из Boost-а", то что тут остается сказать? Разве что "полной ложкой, говорю, черпай!"

PPS. Не в первый раз поднимаю эту тему. Но что поделаешь, если время идет, а смысла в существовании Boost в его привычном виде я как не видел, так и не вижу.

понедельник, 5 декабря 2022 г.

[prog.python.wtf] Понадобилось мне давеча разобраться с Python-овским io... И как-то оно что-то неожиданно непонятно. Вот совсем

В C++ приложение встраивается Python чтобы исполнять Python-вский скрипт прямо внутри приложения. Потребовалось сделать так, чтобы все, что Python-овский скрипт печатает в sys.stdout, отправлялось в C++ную часть.

Стал разбираться с Python-овским io.

Несколько раз проштудировал описание io в стандартной документации.

Мало что понял.

Что удивило. Т.к. пока читаешь, то вроде бы и буквы все понятные, и в слова понятные они складываются. А вот что к чему и почему... Все никак.

Гуглил, читал Stackoverflow, много думал :)

Но просветление не приходило.

Даже в исходники CPython заглянул, чтобы понять, как же вся эта кухня с IOBase, RawIOBase, BufferedIOBase и TextIOBase работает.

В общем, пришел к выводу, что мне нужен стандартный io.TextIOWrapper. В конструктор ему передается экземпляр стандартного io.BufferedWriter. А уже в конструктор io.BufferedWriter в качестве параметра raw передается экземпляр моего класса, наследника io.RawIOBase. Что-то вроде:

понедельник, 12 сентября 2022 г.

[soft.dev.wtf] Это написано по-русски или по-английски, но русскими буквами?

Отличный абзац в статье на русском языке от представителя российской компании:

Каждой фича-команде завели папки в регресс- и смок-сьютах, а в них уже поместили необходимые кейсы. Ну а чтобы в красивый чистый регресс-набор не попадали драфт-кейсы, завели отдельное пространство с кодовым названием “Кандидаты в регресс”. Некий аналог develop-ветки в git’е. Кейсы лежат там до тех пор, пока не пройдут ревью, и не откроется фича-флаг в новом релизе приложения.

цинк.

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

...но какое-то чувство меры таки нужно иметь. Это необходимо, чтобы текст на русском языке оставался именно текстом на русском языке. А не вот это самое.

С другой стороны, я уже старый, да и сильно оторванный от современных подходов к разработке ПО, особенно молодыми, динамичными коллективами. Возможно, это мне уже в силу старческого маразма написанное плохопонятно, а 20-летним синьор-КьюАрам все более чем очевидно? :(

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

[prog.c++] Набиваю шишки с nlohmann::json и uniform initialization syntax

В проекте, который наша маленькая компания сейчас делает под заказ, до недавнего времени использовалась связка из RapidJSON и json_dto. Использовалась буквально в паре-тройке мест и в минимальном объеме.

Но вот пришла необходимость задействовать в проекте JSON более плотно, как основное средство для обмена разнообразной информацией между сущностями в программе. Для чего в проекте RapidJSON и json_dto были заменены на nlohmann::json. Выбран был nlohmann::json поскольку потребовался именно что удобный C++ный DSL для формирования JSON-значений.

До этого у меня опыта работы с nlohmann::json не было, поэтому не удивительно, что умудрился наступить на грабли, обусловленные как самим nlohmann::json, так и C++ным uniform initialization syntax, так и моим дилетантизмом в этой области. О чем сегодня и попробую рассказать.