вторник, 1 января 2030 г.

О блоге

Более тридцати лет я занимался разработкой ПО, в основном как программист и тим-лид, а в 2012-2014гг как руководитель департамента разработки и внедрения ПО в компании Интервэйл (подробнее на LinkedIn). В настоящее время занимаюсь развитием компании по разработке ПО stiffstream, в которой являюсь одним из соучредителей. Поэтому в моем блоге много заметок о работе, в частности о программировании и компьютерах, а так же об управлении.

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

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

[life.photo] Характерный портрет: вы и ваш мир моими глазами. Безвозмездно :)

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

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

понедельник, 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 мы можем обменять аллокаторы для контейнеров.

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

воскресенье, 1 марта 2026 г.

[life.cimena] Очередной кинообзор (2026/02)

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

Фильмы

Человек с Земли (The Man from Earth, 2007). Фильм очень не новый, но посмотрел его только сейчас. Как по мне, так отличное кино, базирующееся на хорошем сюжете и тщательно выстроенных диалогах.

Убежище (Shelter, 2025). Обычный боевик со Стетхемом в главной роли, где круче него только горы и яйца. Но, в отличии от совсем уж сказочных "Пчеловода" и "Мастера", этот смотрится вполне нормально.

Убийца в петле времени (Kill Me Again, 2023). На удивление хорошо. Тот редкий случай, когда не ждешь вообще ничего хорошего, а получаешь нормально сделанное кино с вменяемой развязкой. Ну и, прямо скажем, неожиданная вариация на тему "дня сурка".

Бастион 36 (Bastion 36, 2025). Неплохая криминальная драма. Но мне не хватало динамики, какие-то эпизоды просто проматывал, т.к. ну очень уж скучно было.

Казнить нельзя помиловать (Mercy, 2026). Первые 2/3 было прям хорошо. Затем началось что-то невнятное в виде погони и вокруг нее, что очень сильно испортило впечатление. В конце есть хороший твист, который, к сожалению, испорченное впечатление исправить уже не смог.

Сериалы

Мошенники (второй сезон, 2026). Отличное продолжение отличного сериала.

Ночной администратор (The Night Manager, второй сезон, 2025). Если понравился первый сезон, то можно посмотреть и второй. Местами второй похуже (мне выбор некоторых актеров показался странным), местами получше. Буду ждать продолжения.

Золотое дно (второй сезон, 2025). Похуже первого сезона. Но тут явно просто промежуточный этап, который даже не стали доводить до логического завершения, а просто резко оборвали на половине. Мол ждите третьего сезона. Ну подождем. Хотя вряд ли там будет что-то хорошее.

Вторая семья (первый сезон, 2023). Купился на очень высокий рейтинг на Кинопоиске. Как по мне, средней паршивости. Основной сюжет вроде норм, но некоторые детали оставляют вопросы "ну как так-то?"

Его и её (His & Hers, первый сезон 2026). Смотрибельно, хотя идиотизм некоторых персонажей меня откровенно раздражал. Финал неожиданный, но, как по мне, выглядит несколько чужеродно и притянуто за уши.

Художник (второй сезон, 2025). Начало еще более-менее смотрибельно, но вот завершение -- это просто какой-то позор.

пятница, 27 февраля 2026 г.

[prog.c++] Обеспечивает ли vector::operator=(const vector &) строгую гарантию безопасности исключений?

Если задать какому-нибудь ИИ простой вопрос о гарантиях безопасности исключений оператора присваивания для std::vector, то можно получить однозначный ответ: мол, обеспечивается строгая гарантия. Т.е. если в процессе работы оператора копирования возникнет исключение, то целевой вектор останется в своем исходном виде.

Однако, не все так однозначно™

Пункт первый, далеко не очевидный

std::vector зависит от аллокатора. Хотя, наверное, мало кому доводилось использовать std::vector с аллокатором, отличным от std::allocator. Тем не менее, у вектора есть аллокатор. А у аллокатора есть такое свойство как propagate_on_container_copy_assignment (см., например, здесь). И если это свойство предписывает скопировать в вектор-приемник аллокатор из вектора-источника, то тут возникает тонкий момент: старое содержимое вектора-источника должно быть удалено посредством старого аллокатора.

Если глянуть как этот момент учитывается в реализациях стандартной библиотеки (libstdc++ от GCC или libcxx от LLVM), то можно увидеть, что сперва уничтожается старое содержимое вектора, и лишь затем делается попытка копирования содержимого вектора источника.

Особенно хорошо это видно на примере libstdc++v3:

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

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

Пункт второй, более очевидный

Многое зависит еще и от того, какие гарантии безопасности исключений дает сам тип T, который хранится в vector-е. Если посмотреть на одну из ветвей работы оператора присваивания в libstdc++v3, то можно увидеть, что новое содержимое записывается поверх старого:

А раз так, то мы сильно зависим от того, бросают ли исключения операторы копирования у T. Если бросают, то может случиться следующее:

  • первые N элементов из вектора-источника будут скопированы;
  • на (N+1) элементе возникнет исключение, операция копирования будет прервана.

Получится, что первые N элементов у вектора-приемника получат новые значения, а оставшиеся -- сохранят старые. При это непонятно что будет с (N+1): если у T::operator= строгая гарантия безопасности исключений, то сохранится старое. А вот если нет... Тогда этот элемент окажется в непонятном состоянии.

В сухом же остатке имеем то, что если в операторе копирования для вектора возникнет исключение в T::operator=, то вектор может изменить свое состояние (часть будет иметь новое значение, часть старое). А это никакая не строгая гарантия безопасности.


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

template<typename T, typename Alloc>
void strong_guarantee_copy(vector<T, Alloc> & dest, const vector<T, Alloc> & src)
{
  vector<T, Alloc> fresh_copy{ src, dest.get_allocator() };
  swap(dest, fresh_copy);
}

В этом случае мы сперва получаем копию. Или исключение, если копия не создается по каким-либо причинам. А уже потом перемещаем это новое значение в dest.

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

пятница, 20 февраля 2026 г.

[prog.c++] Дошел до чистого безумия: new(this) another_object_type

Тяжкая судьба C++программиста довела использования в коде трюка, про который еще несколько лет назад и вовсе не знал. Речь про замену типа объекта через вызов placement new внутри метода самого заменяемого объекта.

В моем случае получился код вроде вот такого:

templatetypename ValueT >
void
special_map< ValueT >::fixed_capacity_node::insert_item( ValueT value )
{
   auto & self = this->self_data();
   if( self.size() < self.capacity() )
   {
      ... // use the existing node.
   }
   else
   {
      ... // prepare internal data for a new node type.

      // Replace the node by an instance of the new type.
      new(this) dynamic_capacity_node{ std::move(new_node_internal_data) };
   }
}

Т.е. есть объект типа fixed_capacity_node в котором имеется контейнер для небольшого количества элементов. При вставке очередного элемента может оказаться, что этот контейнер исчерпан и нужно перейти к использованию dynamic_capacity_node вместо fixed_capacity_node. Для чего новый объект dynamic_capacity_node и создается. Но не просто так, а в той области памяти, которую только что занимал старый fixed_capacity_node объект.

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

PS. Никому не советую повторять подобное в домашних условиях. Как говорится, все показанное было выполнено специально подготовленными профессионалами 😎 Были предприняты различные предохранительные меры дабы удостоверится, что fixed_capacty_node и dynamic_capacity_node идентичны и по размеру, и по выравниваю. И что после смены типа объекта никто не обращался к нему по старому указателю без std::launder.

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

[business] Практические выводы из того, что успешный результат в бизнесе не гарантирован

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

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


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

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

И так снова и снова.

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

Вот этот вот переход от "и тогда точно..." к "посмотрим что" оказался ключевым чтобы избавиться от дополнительных переживаний и нервного напряжения. Хотя бы в некоторой степени.


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

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

Только вот, скорее всего, это всего лишь отсрочка неизбежного. И лучше таки резать сразу, не дожидаясь перитонита. Чтобы потерять только N денег, а не N умноженное на X, плюс потраченное впустую дополнительное время, которое вообще никак не вернуть.


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

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

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

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


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

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

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

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

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


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

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

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

Отсюда еще один, если можно так сказать, совет: не стоит затягивать с попыткой начать свое дело. Если в 35 вы задумались о том, что пора открывать что-то свое, то лучше не откладывать это до 45. Можно попробовать в 36 и к 45 совершить несколько попыток. Либо что-то увенчается успехом, либо вы поймете, что в найме все-таки спокойнее и денежнее и будете тихо встречать неизбежно приближающуюся старость ;)