tag:blogger.com,1999:blog-6542790833902758422024-03-18T12:47:48.195+03:00Размышлизмы eao197Размышления и впечатления, которые не хочется держать в себе. О программировании в частности. Ну и о творчестве, и о жизни вообще.eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.comBlogger3644125tag:blogger.com,1999:blog-654279083390275842.post-29054401499490916852030-01-01T00:00:00.001+03:002021-08-30T09:10:34.651+03:00О блоге<p>Более двадцати лет я занимался разработкой ПО, в основном как программист и тим-лид, а в 2012-2014гг как руководитель департамента разработки и внедрения ПО в компании Интервэйл (подробнее на <a href="http://www.linkedin.com/in/eao197/">LinkedIn</a>). В настоящее время занимаюсь развитием компании по разработке ПО <a href="http://stiffstream.com"><b>stiff</b>stream</a>, в которой являюсь одним из соучредителей. Поэтому в моем блоге много заметок <a href="http://eao197.blogspot.com/search/label/%D0%9E%20%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%B5">о работе</a>, в частности <a href="http://eao197.blogspot.com/search/label/%D0%9E%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B8">о программировании</a> и <a href="http://eao197.blogspot.com/search/label/%D0%9E%20%D0%BA%D0%BE%D0%BC%D0%BF%D1%8C%D1%8E%D1%82%D0%B5%D1%80%D0%B0%D1%85">компьютерах</a>, а так же <a href="http://eao197.blogspot.com/search/label/%D0%9E%D0%B1%20%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B8">об управлении</a>.</p>
<p>Так же я пишу <a href="http://eao197.blogspot.com/search/label/%D0%9E%20%D0%B6%D0%B8%D0%B7%D0%BD%D0%B8">о жизни</a> вообще и о нескольких своих увлечениях: <a href="http://eao197.blogspot.com/search/label/Foto">о фотографии</a> (включая публикацию <a href="http://eao197.blogspot.com/search/label/%D0%9C%D0%BE%D0%B8%20%D1%84%D0%BE%D1%82%D0%BE">своих фотографий</a>, некоторые есть и на <a href="http://www.zeissimages.com/standardgallery.php?puid=2701&showall">ZeissImages</a>), <a href="http://eao197.blogspot.com/search/label/%D0%A1%D0%BF%D0%BE%D1%80%D1%82">о спорте</a>, особенно <a href="http://eao197.blogspot.com/search/label/%D0%94%D0%B0%D1%80%D1%82%D1%81">о дартсе</a>, и, совсем коротко, <a href="http://eao197.blogspot.com/search/label/%D0%9A%D0%B8%D0%BD%D0%BE">о кино</a>.</p>eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.comtag:blogger.com,1999:blog-654279083390275842.post-27705629797603562232029-12-31T10:53:00.000+03:002018-04-27T17:06:39.634+03:00[life.photo] Характерный портрет: вы и ваш мир моими глазами. Безвозмездно :)<p>Вы художник? Бармен или музыкант? Или, может быть, коллекционер? Плотник или столяр? Кузнец или слесарь? Владеете маленьким магазинчиком или управляете большим производством? Реставрируете старинные часы или просто починяете примус? Всю жизнь занимаетесь своим любимым делом и хотели бы иметь фото на память?</p>
<p><img src="https://lh3.googleusercontent.com/-aZCbn7SBAWQ/Vg1grRZcgHI/AAAAAAAAbBs/zKzewokAjb4/s800-Ic42/portrait_collage_20151001.jpg"/></p>
<p>Предлагаю сделать портрет в обстановке, связанной с вашей работой или увлечением. Абсолютно бесплатно. Очень уж мне нравится фотографировать людей в их естественной среде. Происходить это может так...</p>
<a name='more'></a>
<p>...Вы делаете то, что умеете и любите, попутно рассказывая о себе и своей профессии/хобби. Я же становлюсь вашей тенью и/или собеседником, снимаю кадр за кадром в погоне за наиболее удачным снимком. Если что-то получается, то вам достаются фотографии в электронном виде, а мне -- возможность их некоммерческого использования в блоге и соцсетях.</p>
<p>Если вы заинтересовались, то связаться со мной можно по мейлу eao197 на gmail точка com или в Skype по имени eao197. Либо же оставив комментарий к этой записи. Или в соцсетях: <a href="http://vk.com/eao197">VK</a>, <a href="https://www.facebook.com/eao197">Facebook</a>.</p>
<p>Для съемки мне нужно знать в каких условиях будет проходить фотосессия: помещение или открытое пространство, темно там или светло, можно ли использовать вспышку или это будет невозможно в принципе и т.д. Если мое присутствие на вашем рабочем месте или рядом с ним требует какого-то предварительного согласования -- то вам самим нужно будет позаботиться об этом. Ну и было бы желательно, чтобы ваши коллеги и клиенты были в курсе и не протестовали бы против попадания в кадр.</p>
<p>Во избежание чрезмерных ожиданий хочу сразу предупредить: я всего лишь любитель, не проффи. Поэтому результат не гарантирован. По опыту, для того, чтобы получился хороший портрет, приоткрывающий характер человека, нужен не один дубль, и даже не десять, а то и не сто. Ориентироваться нужно на 3-4 часа общения и фотосьемки, что выльется в несколько сотен кадров из которых будет отобрано всего несколько штук. Так что вам потребуется немного терпения, а мне -- капелька удачи.</p>
<p>Вот пример того, что может получаться в результате таких съемок:</p>
<ul>
<li>кукольных дел мастер <a href="https://goo.gl/photos/khfXG6kGQ52yELyu7">Екатерина Katiedda Минакова</a>;</li>
<li>саксофонист <a href="https://goo.gl/photos/t2t7v58jgVRpN4Xr8">Анатолий Аксёнов</a></li>
<li>резчик по дереву <a href="https://plus.google.com/photos/117023091752061088817/albums/6178693720360830017">Виктор Россолов</a>;</li>
<li>уникальный гармонист <a href="https://goo.gl/photos/k4PGyrA7MzYqf8Zu7">Вячеслав Сивуха</a>;</li>
<li>кузнец <a href="https://goo.gl/photos/Zq9YH84PQjXCYpgG7">Дмитрий Андреев</a>;</li>
<li>реконструктор <a href="https://goo.gl/photos/YF8vhEwEKfrPURo47">Виталий Медведев</a></li>
</ul>
<p>Вот еще несколько примеров того, что получалось в прошлом:</p>
<p><img src="https://lh4.googleusercontent.com/-nNzBnuGZsrU/VTLNTXTkUPI/AAAAAAAATJQ/uOqHTshyJXA/w1236-h824-no/20150412-144413-KLN_3957.jpg" width="800"/></p>
<!-- Танго на набережной -->
<p><img src="https://lh4.googleusercontent.com/-PTVqY2x3lRQ/VTNXF-H6BAI/AAAAAAAATKQ/9OediZ9x_3c/w548-h823-no/20130719-200026-20130719-200026.jpg" height="800"/></p>
<!-- День нептуна -->
<p><img src="https://lh5.googleusercontent.com/-Gf8V1QBU--8/VTNS5SEhWkI/AAAAAAAATJo/4Qh6r0MlA5M/w1398-h824-no/20130713-123844-20130713-123844.jpg" width="800"/></p>
<!-- -->
<p><img src="http://www.zeissimages.com/gallery/2701/U2701I1409694203.SEQ.0.jpg" width="800"/></p>
<p><img src="http://www.zeissimages.com/gallery/2701/U2701I1402041657.SEQ.0.jpg" width="800"/></p>
<p><img src="http://www.zeissimages.com/gallery/2701/U2701I1401782657.SEQ.0.jpg" width="800"/></p>
<!-- Сергей Вяхирев -->
<p><img src="http://www.zeissimages.com/gallery/2701/U2701I1396898490.SEQ.0.jpg" width="800"/></p>
<!-- Скрипачка за столом -->
<p><img src="http://www.zeissimages.com/gallery/2701/U2701I1394868675.SEQ.0.jpg" width="800"/></p>
<!-- Гитарист -->
<p><img src="http://www.zeissimages.com/gallery/2701/U2701I1394868546.SEQ.0.jpg" width="800"/></p>
<!-- Коллекционер -->
<p><img src="http://www.zeissimages.com/gallery/2701/U2701I1378559938.SEQ.0.jpg" width="800"/></p>
<!-- Музыканты в Минске под дождем -->
<p><img src="http://www.zeissimages.com/gallery/2701/U2701I1377513249.SEQ.0.jpg" width="800"/></p>
<!-- Портрет фотографа -->
<p><img src="https://lh6.googleusercontent.com/-qzamQfaTzBA/UxTEOz9x2GI/AAAAAAAAM5U/JsIDxeMk7Es/w800-h532-no/20140302-141423.jpg" width="800"/></p>
<!-- Реконструкторы в Гомеле на Прудах в 2013-м -->
<p><img src="https://lh6.googleusercontent.com/-VIXnoSAFLBM/UdfPfSOENcI/AAAAAAAAJwA/O_ADRvK4uUA/w640-h800-no/20130703-174153.jpg" height="800"/></p>
<p><img src="https://lh4.googleusercontent.com/-Wa6gJw-Fy3k/UdfNQ551KEI/AAAAAAAAJqE/SScIVkBJXV4/w800-h532-no/20130703-171342.jpg" width="800"/></p>
<!-- С первенства РБ по ката (кёкусинкай каратэ, 2015) -->
<p><img src="https://lh4.googleusercontent.com/-92-k5v0WGuI/VUdNAdO3xlI/AAAAAAAATmQ/YuaRypGJ22w/w1238-h824-no/20150503-105021-KLN_4268.jpg" width="800"/></p>
<p><img src="https://lh3.googleusercontent.com/-PS51zzbbaHY/VUdNPjSMoGI/AAAAAAAATng/3ls7b3otTkM/w948-h824-no/20150503-113301-KLN_4471.jpg" width="800"/></p>
<!-- С выступления рестлеров на 9-е мая 2015 -->
<p><img src="https://lh4.googleusercontent.com/-oCFclpD2_ro/VU9koZ87uZI/AAAAAAAATys/CKEaWiR4eeU/w1238-h824-no/20150509-163040-KLN_5585.jpg" width="800"/></p>
<!-- Резчик по дереву Андрей Пастухов (9-е мая 2015) -->
<p><img src="https://lh6.googleusercontent.com/-QPBnGT2IlIA/VU4uhjxKe2I/AAAAAAAATuc/uDRvBtbkezY/w548-h823-no/20150509-115629-KLN_5343.jpg" height="800"/></p>
<!-- Пожилой коллекционер на 9-е мая 2015 -->
<p><img src="https://lh6.googleusercontent.com/-1gs2O_WMKy0/VVCWCD9WuJI/AAAAAAAAT9Q/oMK0n01ndz0/w548-h823-no/20150509-123258-KLN_5434.jpg" height="800"/></p>
<!-- Саксофонист Анатолий Аксёнов 31-го мая 2015 -->
<p><img src="https://lh3.googleusercontent.com/5J1hAzKKxIJ8cWSUKEYFz8N38p0-XWdti2nKAKiHcbKT=w1374-h914-no" width="800"/></p>
<!-- Гончар со свистулькой в гомельском парке 31-го мая 2015 -->
<p><img src="https://lh3.googleusercontent.com/Y-VsIXJ-7e6WHRRfLCE9i9A9abQsC09h58uizCq_u4ZW=w1143-h914-no" width="800"/></p>
<!-- Клоун на акции "День красного носа" 31-го мая 2015 -->
<p><img src="https://lh3.googleusercontent.com/sKAG8QzcezlhlRVt4utZvOn_kF3QGIG4kcQdHdD8XIcT=w1458-h914-no" width="800"/></p>
<!-- Екатерина Минакова на мастерклассе по изготовлению кукол-тыквоголовок 20 июня 2015 -->
<p><img src="https://lh3.googleusercontent.com/KOIFCIgONauxZmTsFOLItP03C497eVrE975XQxqwDHPO=w800" width="800"/></p>
<!-- Два музыканта у ГЦК 24-го июня 2015 -->
<p><img src="https://lh3.googleusercontent.com/g6TVDqw_KDe8HU0ez_WDdzF9s09bIPwHuBBJyyX9eO9z=w800" width="800"/></p>
<p><img src="https://lh3.googleusercontent.com/PO9u-L3Mm98kouBhEgciX_EgMB-k2Xsece2c39Fm0hRX=w800" width="800"/></p>
<!-- Виктор Россолов, резчик по дереву -->
<p><img src="https://lh3.googleusercontent.com/vr_SGGddsTFrdLus3Ijt4FE327I5uEnwfUH43NbWvx-9=h800"/></p>
<p><img src="https://lh3.googleusercontent.com/60OdI0GJebJIYKhEK12Woo7Xi5vQcxPL20Wx387flb6x=h800"/></p>
<p><img src="https://lh3.googleusercontent.com/_lOXH-5eFqhafs6cL-ln41_chTlD4l_ZvGsQL0W8vh5T=w800"/></p>
<!-- Рыбак на соревнования по ловле рыбы фидером 2-го августа 2015 -->
<p><img src="https://lh3.googleusercontent.com/ijj1B7UIqM7S3G2NTUFuJM2Y3kYTKwl7W_7lFZi-Ps0r=w800"/></p>
<!-- Вячеслав Сивуха 29-го августа 2015 -->
<p><img src="https://lh3.googleusercontent.com/5iSPB3kemPo7dm2PJtN_fQL8INvl0qoGqReCpZFovDOh=w800"/></p>
<!-- С фестиваля "Минск Старажытны" 12-го сентября 2015 -->
<p><img src="https://lh3.googleusercontent.com/-8WF-GpHYL9I/VfZ54CVKCfI/AAAAAAAAYus/th-inQi-Dl8/s800-Ic42/20150912-151417-DSC_0882.jpg"/></p>
<p><img src="https://lh3.googleusercontent.com/-xdF1xruVwPU/VfZ6Ry7Ct0I/AAAAAAAAYuc/sdTIZbvrTGA/s800-Ic42/20150912-152641-DSC_0925.jpg"/></p>
<!-- Кузнец Дмитрий Андреев (23-е сентября 2015) -->
<p><img src="https://lh3.googleusercontent.com/-MlHMx7fPrXs/VgTx68NHi5I/AAAAAAAAaCY/-4Nbc9VwUdc/s800-Ic42/20150923-123159-DSC_1473.jpg"/></p>
<p><img src="https://lh3.googleusercontent.com/-Tv5563SEWWc/VgTx66jsEhI/AAAAAAAAaCY/r4hagwZhi3E/s800-Ic42/20150923-130645-DSC_1628.jpg"/></p>
<!-- Реконструктор Виталий Медведев (9-е мая 2016) -->
<p><img src="https://lh3.googleusercontent.com/-lCqNUrhefr8/VzCsdcnpsYI/AAAAAAAAdVE/lMHV5QgdqVgo7M-j5SgOpGUzFgxsGTaeACCo/s800/20160509-140603-DSC_6134.jpg"/></p>
<p><img src="https://lh3.googleusercontent.com/-h2S6gSf3Nks/VzCtigPdDzI/AAAAAAAAdVE/vPKxaWLODgA3sSY-V8MnWzsEDOcwOB3iACCo/s800/20160509-141604-DSC_6195-2.jpg"/></p>
<!-- Бородач за компьютером на CoreHard Spring 2017 (13-е мая 2017)) -->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/152498119@N03/34349688134/in/photostream/" title="20170513-112141-DSC_7774"><img src="https://c1.staticflickr.com/5/4277/34349688134_2621e0ef2b_c.jpg" width="800" height="534" alt="20170513-112141-DSC_7774"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!-- Дори Экстерман на CoreHard Spring 2017 (13-е мая 2017)) -->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/152498119@N03/34349687964/in/photostream/" title="20170513-120348-DSC_7816"><img src="https://c1.staticflickr.com/5/4274/34349687964_ef67a1b270_c.jpg" width="800" height="534" alt="20170513-120348-DSC_7816"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!-- ДиДжей на Фестивале городской еды (27-е августа 2017) -->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/152498119@N03/36910824456/in/dateposted/" title="20170827-142946-DSC_9303"><img src="https://farm5.staticflickr.com/4377/36910824456_4a02bc95a5_c.jpg" width="534" height="800" alt="20170827-142946-DSC_9303"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!-- Жонглер на Дне Красного носа (27-е августа 2017) -->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/152498119@N03/36957765561/in/photostream/" title="20170827-134632-DSC_9214"><img src="https://farm5.staticflickr.com/4346/36957765561_689280daeb_c.jpg" width="534" height="800" alt="20170827-134632-DSC_9214"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!-- Клоун на мотоцикле на Дне Красного носа (27-е августа 2017) -->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/152498119@N03/37099333605/in/photostream/" title="20170827-131334-DSC_9010"><img src="https://farm5.staticflickr.com/4375/37099333605_dce23edd09_c.jpg" width="800" height="534" alt="20170827-131334-DSC_9010"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!-- Скрипач на Невском в Питере (апрель 2018) -->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/152498119@N03/27868900178/in/dateposted/" title="20180422-155704-DSCF0015"><img src="https://farm1.staticflickr.com/912/27868900178_ceeabf5305_c.jpg" width="800" height="533" alt="20180422-155704-DSCF0015"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!-- Гитарный мастер Евгений в Питере (апрель 2018) -->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/152498119@N03/26800424547/in/photostream/" title="20180422-143118-DSCF9903"><img src="https://farm1.staticflickr.com/825/26800424547_cede9a2743_c.jpg" width="800" height="533" alt="20180422-143118-DSCF9903"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<p>А можно отнестись к этой затее и более широко. Не обязательно иметь экзотическую профессию или уникальное хобби. Достаточно быть неординарным человеком и быть готовым провести несколько часов под прицелом фотоаппарта, желательно в подходящем антураже...</p>
<!-- Девушка из Танго на набережной -->
<p><img src="https://lh6.googleusercontent.com/-e-uKGuGEHpE/VTNXFyix6fI/AAAAAAAATKU/fU4id6su968/w1238-h824-no/20130719-195618-20130719-195618.jpg" width="800"/></p>
<!-- Лёша Юша в Минске -->
<p><img src="https://lh4.googleusercontent.com/-eXkg8sUTB_w/UrAMjvWIJzI/AAAAAAAAMp0/z4Ti2O_Gu7k/w800-h569-no/20131215-114227.jpg" width="800"/></p>
<!-- Андрей Тарасенко на Belarus Open -->
<p><img src="https://lh4.googleusercontent.com/-BmOUcVwZwMo/Uh3AcByOvNI/AAAAAAAAKgU/dafYeZpmDcY/w800-h532-no/20130824-201046.jpg" width="800"/></p>
<p><img src="https://lh4.googleusercontent.com/-KEyKHI0SU5Q/U2aumUkdyOI/AAAAAAAANgA/jeLVNF6WTnw/w800-h532-no/20140504-203526.jpg" width="800"/></p>
<p><img src="http://www.zeissimages.com/gallery/2701/U2701I1420460994.SEQ.0.jpg" width="800"/></p>
<p><img src="https://lh4.googleusercontent.com/-s7jbG0_tpd0/UsmLS9QKSoI/AAAAAAAAMvw/WGG6MgomiiA/w800-h640-no/20131215-082410.jpg" width="800"/></p>
<!-- Алексей Голубев 1-го сентября 2015 -->
<p><img src="https://lh3.googleusercontent.com/BGbKe5oFGK5IVFCV3UVr6VxrVh0T3lyh-v6znH7-AfGL=w800"/></p>
<!-- Девушка играет на свистульке. Минск Старажытны, 12-го сентябры 2015 -->
<p><img src="https://lh3.googleusercontent.com/-7DfSQ09nHek/VfZ3broKl-I/AAAAAAAAYuk/K7bqf_eyghA/s800-Ic42/20150912-144846-DSC_0820-2.jpg"/></p>
<!-- Алексей Юша (Минск Старажытны, 12-го сентября 2015) -->
<p><img src="https://lh3.googleusercontent.com/-Nc_FuP5LDtU/VfSYoYmie3I/AAAAAAAAYpk/-kBxsH_Sl4w/s800-Ic42/20150912-125946-DSC_0471.jpg"/></p>
<!-- Владислав Волков (Москва, 9-е февраля 2016) -->
<p><img src="https://lh3.googleusercontent.com/-WN3itMJauo4/VrzPfMnmc6I/AAAAAAAAcfg/mOakOtSPe-0/s800-Ic42/20160209-124944-DSCF2643.jpg"/></p>
<!-- Коля Шмаков на CoreHard Spring 2017 (13-е мая 2017)) -->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/152498119@N03/34349688574/in/dateposted/" title="20170513-103826-DSC_7656"><img src="https://c1.staticflickr.com/5/4290/34349688574_d1b88f8f08_c.jpg" width="800" height="534" alt="20170513-103826-DSC_7656"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!-- Ретро-фотограф на Дне Города (16-е сентября 2017) -->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/152498119@N03/36447681394/in/dateposted/" title="20170916-142459-DSC_9446"><img src="https://farm5.staticflickr.com/4408/36447681394_1c98df6081_c.jpg" width="800" height="534" alt="20170916-142459-DSC_9446"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!-- Музыканты на Дне Города (16-е сентября 2017) -->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/152498119@N03/36887823000/in/photostream/" title="20170916-154013-DSC_9543"><img src="https://farm5.staticflickr.com/4331/36887823000_2d451b3b7a_c.jpg" width="800" height="534" alt="20170916-154013-DSC_9543"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0tag:blogger.com,1999:blog-654279083390275842.post-15671186407953436892024-03-14T08:02:00.001+03:002024-03-14T08:02:19.235+03:00[work] Открыт для сотрудничества в качестве C++ разработчика<p>В виде (суб)контракта с нашей компанией <a href="https://stiffstream.com">СтифСтрим</a>.</p>
<p>Прикладной специализации не имею, за тридцать лет в программизме приходилось заниматься разными вещами. Очень часто это были инфраструктурные вещи -- фреймворки, библиотеки и утилиты, которые затем использовались для решения прикладных задач. В последние годы поддерживал и развивал C++ные библиотеки <a href="https://github.com/Stiffstream/sobjectizer">SObjectizer</a>, <a href="https://github.com/Stiffstream/restinio">RESTinio</a> и <a href="https://github.com/Stiffstream/json_dto">json_dto</a>.</p>
<p>Самостоятельно погружаюсь в проблему, нахожу решение, кодирую, тестирую, документирую. Если нужно обучаю. Если нужно сопровождаю и поддерживаю. Если нужно выступаю в качестве евангелиста (см. <a href="https://habr.com/ru/users/eao197/publications/articles/">список публикаций на Хабре</a>).</p>
<p>Работаю не быстро, но качественно, беру недорого.</p>
<p>Оценить мой уровень можно, например, про проекту <a href="https://github.com/Stiffstream/arataga">aragata</a>, реализованному мной практически в одиночку. Код можно увидеть на <a href="https://github.com/Stiffstream/arataga">GitHub-е</a>, на Хабре есть две статьи о том, что это и как работает: <a href="https://habr.com/ru/articles/537616/">вводная статья</a> и <a href="https://habr.com/ru/articles/559356/">описание сделанных по результатам нагрузочных испытаний оптимизаций</a> + вот <a href="https://eao197.blogspot.com/2022/02/progc-aragata.html">этот пост</a>.</p>
<p>В качестве дополнительных примеров: <a href="https://sourceforge.net/p/sobjectizer/repo/HEAD/tree/tags/timertt/1.2.3/">timertt</a> (+ <a href="https://sourceforge.net/p/sobjectizer/wiki/timertt%201.2/">документация</a>), <a href="https://github.com/Stiffstream/so5extra">so5extra</a> (+ <a href="https://github.com/Stiffstream/so5extra/wiki">документация</a>) -- эти проекты так же написанные мной самостоятельно.</p>
<!--
<p>Супер-пупер программистом не являюсь, случаются и ошибки, и слишком сложные и/или не самые оптимальные решения. Еще из недостатков: рублю правду-матку не взирая на лица и не играю в интриги/политику.</p>
<p>Поучаствовал бы в проекте в области server-side или middleware, в котором качество кода не пустой звук.</p>
-->
<!--
<p>В ближайшие несколько месяцев доступен лишь в режиме part-time, потом будем посмотреть, не устанете ли вы от моей прямоты и занудности.</p>
-->
<p>Связаться со мной можно через eao197 на gmail тчк com. Если кому-то интересен профиль на LinkedIn, то <a href="https://www.linkedin.com/in/eao197/">вот</a>.</p>
<hr/>
<p>Это сообщение повисит какое-то время вверху. Потом будет видно, имеет ли смысл пытаться дальше оставаться в C++.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com4tag:blogger.com,1999:blog-654279083390275842.post-79865291637610325242024-03-14T08:02:00.000+03:002024-03-14T08:02:03.474+03:00[prog.flame] Самодокументирующися код против документированного, наглядно<p>Намедни <a href="https://www.linkedin.com/posts/eao197_%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B5%D1%81%D0%BD%D0%BE-%D0%B0-%D0%BA%D0%B0%D0%BA-%D1%82%D0%B0%D0%BA-%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B0%D0%B5%D1%82%D1%81%D1%8F-%D1%87%D1%82%D0%BE-%D0%B2%D0%B7%D1%80%D0%BE%D1%81%D0%BB%D1%8B%D0%B5-activity-7173220866011357186-YFqM">в LinkedIn поиронизировал на счет "самодокументирующегося кода"</a>. В очередной раз 😎</p>
<p>В очередной раз с удивлением для себя обнаружил людей, скептически относящихся к необходимости комментировать код.</p>
<p>Подумал, что лучше одна иллюстрация лучше тысячи слов. Поэтому вот пример кода из реального проекта. Изменены только названия сущностей, все комментарии оставлены как есть. Плюс изъяты фрагменты, не относящиеся к самой иллюстрации.</p>
<p>Каждый сам может для себя решить, с каким кодом ему проще было бы разбираться. Сам-то я для себя уже давно выводы сделал. Поэтому и пишу комментарии, хотя тут мне еще есть куда расти, к сожалению. Не всегда получается сделать хорошо с первого раза 🙁</p>
<p>Под катом оригинальный фрагмент с комментариями. Такими, какими они и были написаны при разработке. Но сперва этот же фрагмент, но вообще без комментариев. Именно так "самодокументирующися" код и выглядит, по моему (не)скромному опыту.</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#90f020">class</font> DefaultThreadPoolScheduler <font color="#90f020">final</font> : <font color="#ffff60">public</font> Scheduler<br>
{<br>
...<br>
<br>
<font color="#ffff60">private</font>:<br>
<font color="#90f020">struct</font> WorkerData<br>
{<br>
std::mutex _lock;<br>
std::condition_variable _wakeupCondition;<br>
<br>
Scheduler::TaskUniquePtr _taskToRun;<br>
<br>
<font color="#90f020">bool</font> _shutdownInitiated{ <font color="#ffa0a0">false</font> };<br>
};<br>
<br>
<font color="#ffff60">using</font> WorkerDataContainer =<br>
std::vector<std::reference_wrapper<WorkerData>>;<br>
<br>
std::latch _allWorkersStartedLatch;<br>
<br>
std::mutex _lock;<br>
<br>
TasksContainer _tasksQueue;<br>
<br>
ThreadPool _threadPool;<br>
<br>
WorkerDataContainer _availableWorkers;<br>
<br>
<font color="#90f020">bool</font> _shutdown{ <font color="#ffa0a0">false</font> };<br>
};<br>
<br>
<font color="#90f020">void</font> DefaultThreadPoolScheduler::doWork() <font color="#ffff60">noexcept</font><br>
{<br>
WorkerData thisWorkerData;<br>
<br>
{<br>
std::lock_guard schedulerLock{ _lock };<br>
_availableWorkers.push_back(std::ref(thisWorkerData));<br>
<br>
_allWorkersStartedLatch.count_down();<br>
}<br>
<br>
<font color="#90f020">bool</font> shutdownIntitiated{ <font color="#ffa0a0">false</font> };<br>
<font color="#ffff60">while</font>( !shutdownIntitiated )<br>
{<br>
std::unique_lock workerLock{ thisWorkerData._lock };<br>
<br>
<font color="#ffff60">if</font>( TaskUniquePtr taskToRun = std::move(thisWorkerData._taskToRun); !taskToRun )<br>
{<br>
shutdownIntitiated = thisWorkerData._shutdownInitiated;<br>
<font color="#ffff60">if</font>( !shutdownIntitiated )<br>
{<br>
thisWorkerData._wakeupCondition.wait(workerLock);<br>
<br>
shutdownIntitiated = thisWorkerData._shutdownInitiated;<br>
}<br>
}<br>
<font color="#ffff60">else</font><br>
{<br>
workerLock.unlock();<br>
taskToRun->run(Scheduler::RunCondition::Normal);<br>
completeTaskThenTryGetNext(std::move(taskToRun), thisWorkerData);<br>
}<br>
}<br>
}<br>
</font></td>
</tr>
</tbody></table>
<a name='more'></a>
<p>Ну а вот исходник с комментариями:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#90f020">class</font> DefaultThreadPoolScheduler <font color="#90f020">final</font> : <font color="#ffff60">public</font> Scheduler<br>
{<br>
...<br>
<br>
<font color="#ffff60">private</font>:<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Описание одной свободной рабочей нити.</font><br>
<font color="#ffa500">///</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Каждая рабочая нить создает такой объект у себя на стеке,</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">а объект-шедулер хранит у себя ссылки на эти объекты.</font><br>
<font color="#90f020">struct</font> WorkerData<br>
{<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Замок для обеспечения thread-safety.</font><br>
std::mutex _lock;<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Для пробуждения рабочей нити когда для нее есть task или</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">же когда нужно завершить работу.</font><br>
std::condition_variable _wakeupCondition;<br>
<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Task который должен быть запущен на контексте</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">этой рабочей нити.</font><br>
<font color="#ffa500">///</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Пустое значение указывает, что в данный момент Task-а нет.</font><br>
Scheduler::TaskUniquePtr _taskToRun;<br>
<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Признак необходимости завершить работу.</font><br>
<font color="#ffa500">///</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Если выставлен в true, то больше никаких действий рабочая</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">нить предпринимать не должна.</font><br>
<font color="#90f020">bool</font> _shutdownInitiated{ <font color="#ffa0a0">false</font> };<br>
};<br>
<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Тип контейнера с информацией о свободных рабочих нитях.</font><br>
<font color="#ffff60">using</font> WorkerDataContainer =<br>
std::vector<std::reference_wrapper<WorkerData>>;<br>
<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Барьер для синхронизации запуска рабочих нитей.</font><br>
<font color="#ffa500">///</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Нужен для того, чтобы после запуска thread-pool-а убедиться в том,</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">что все рабочие нити успели войти в doWork и добавили себя</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">в _availableWorkers.</font><font color="#80a0ff"> </font><font color="#ffff60">Если этого не сделать, то может случиться так,</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">что конструктор DefaultThreadPoolScheduler завершится раньше, чем на</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">рабочих нитях произойдет вход в doWork.</font><font color="#80a0ff"> </font><font color="#ffff60">В этом случае мы сможем</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">начать получать новые заявки еще до того, как во _availableWorkers</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">появится информация о свободных рабочих нитях.</font><font color="#80a0ff"> </font><font color="#ffff60">А это приведет к тому,</font><br>
<font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">что запросы будут становиться в очередь, но извлекаться оттуда не будут.</font><br>
std::latch _allWorkersStartedLatch;<br>
<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Замок объекта для обеспечения thread safety.</font><br>
std::mutex _lock;<br>
<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Очередь запросов.</font><br>
TasksContainer _tasksQueue;<br>
<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Пул рабочих потоков для обслуживания запросов.</font><br>
ThreadPool _threadPool;<br>
<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Список свободных рабочих нитей.</font><br>
WorkerDataContainer _availableWorkers;<br>
<br>
<span style="background-color: #000040"><font color="#c0c0c0"> </font></span><font color="#ffa500">///</font><font color="#80a0ff"> </font><font color="#ffff60">Признак того, что работа шедулера была завершена.</font><br>
<font color="#90f020">bool</font> _shutdown{ <font color="#ffa0a0">false</font> };<br>
};<br>
<br>
<font color="#90f020">void</font> DefaultThreadPoolScheduler::doWork() <font color="#ffff60">noexcept</font><br>
{<br>
WorkerData thisWorkerData;<br>
<br>
<font color="#80a0ff">// Изначально эта нить свободна и шедулер должен об этом узнать.</font><br>
{<br>
std::lock_guard schedulerLock{ _lock };<br>
_availableWorkers.push_back(std::ref(thisWorkerData));<br>
<br>
<font color="#80a0ff">// Информируем, что еще одна нить внесла себя в _availableWorkers.</font><br>
<font color="#80a0ff">// Если этого не сделать, то конструктор DefaultThreadPoolScheduler не</font><br>
<font color="#80a0ff">// завершит свою работу.</font><br>
_allWorkersStartedLatch.count_down();<br>
}<br>
<br>
<font color="#90f020">bool</font> shutdownIntitiated{ <font color="#ffa0a0">false</font> };<br>
<font color="#ffff60">while</font>( !shutdownIntitiated )<br>
{<br>
std::unique_lock workerLock{ thisWorkerData._lock };<br>
<br>
<font color="#ffff60">if</font>( TaskUniquePtr taskToRun = std::move(thisWorkerData._taskToRun); !taskToRun )<br>
{<br>
<font color="#80a0ff">// Если оказались здесь, значит очередного запроса для обработки нет,</font><br>
<font color="#80a0ff">// нужно его подождать, но делать это можно только если не начали shutdown.</font><br>
shutdownIntitiated = thisWorkerData._shutdownInitiated;<br>
<font color="#ffff60">if</font>( !shutdownIntitiated )<br>
{<br>
<font color="#80a0ff">// Спонтанные пробуждения здесь не страшны. Если нас</font><br>
<font color="#80a0ff">// случайно разбудили, то на следующей итерации мы просто</font><br>
<font color="#80a0ff">// уснем еще раз.</font><br>
thisWorkerData._wakeupCondition.wait(workerLock);<br>
<br>
<font color="#80a0ff">// Еще раз обновляемся, т.к. shutdown мог начаться пока мы ждали.</font><br>
shutdownIntitiated = thisWorkerData._shutdownInitiated;<br>
}<br>
}<br>
<font color="#ffff60">else</font><br>
{<br>
<font color="#80a0ff">// Выполнять заявку нужно при незаблокированном замке рабочей нити,</font><br>
<font color="#80a0ff">// в противном случае получим проблемы в completeTaskThenTryGetNext.</font><br>
workerLock.unlock();<br>
taskToRun->run(Scheduler::RunCondition::Normal);<br>
completeTaskThenTryGetNext(std::move(taskToRun), thisWorkerData);<br>
}<br>
}<br>
}<br>
</font></td>
</tr>
</tbody></table>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com2tag:blogger.com,1999:blog-654279083390275842.post-25093322051608471072024-03-11T16:39:00.000+03:002024-03-11T16:39:44.897+03:00[prog.c++] Первая попытка написать собственный концепт<p>В проекте для одного из клиентов уже используется C++20, так что есть возможность на практике прикоснуться к некоторым фичам двадцатого стандарта. Вроде ranges и concepts.</p>
<p>Добавил в проект кусок кода на обычных C++ные шаблонах. А потом подумал, что в паре мест там можно было бы попробовать задействовать концепты. Т.к. опыта с концептами не было, то попытался в очередной раз разобраться с этой темой и сделать что-то работающее.</p>
<p>Нужно сказать, что к теме концептов подступался уже несколько раз. В смысле читал разные статьи, рассказывающие о том, что такое концепты. Но, т.к. это все была только теория без практики, то "в одно ухо влетало, в другое вылетало". Тут же при попытке написать свой первый концепт пришлось все перечитывать заново 🙂 При этом оказалось, что моя задачка не похожая на те, что разбираются в обзорных статьях про концепты. Так что потребовалось не только перечитать, но еще и покурить бамбук.</p>
<p>В общем, смысл в том, что есть интерфейс диспетчера задач Scheduler и есть интерфейс отдельной задачи Task:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#90f020">class</font> Scheduler<br>
{<br>
<font color="#ffff60">public</font>:<br>
<font color="#90f020">class</font> Task<br>
{<br>
<font color="#ffff60">public</font>:<br>
<font color="#90f020">enum</font> <font color="#90f020">class</font> Status { Normal, Deleted };<br>
<br>
<font color="#90f020">virtual</font> ~Task() = <font color="#ffff60">default</font>;<br>
<br>
<font color="#90f020">virtual</font> <font color="#90f020">void</font> run(Status) = <font color="#ffa0a0">0</font>;<br>
};<br>
<br>
<font color="#ffff60">using</font> TaskUniquePtr = std::unique_ptr<Task>;<br>
<br>
<font color="#90f020">virtual</font> <font color="#90f020">void</font> push(TaskUniquePtr task) = <font color="#ffa0a0">0</font>;<br>
};<br>
</font></td>
</tr>
</tbody></table>
<p>Существует несколько реализаций Scheduler-ов. В качестве же Task-ов планировалось использовать лямбда-функции, которые должны оборачиваться в реализацию интерфейса Scheduler::Task. Т.е. вместо того, чтобы делать как-то так:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#90f020">class</font> FirstTask <font color="#90f020">final</font> : <font color="#ffff60">public</font> Scheduler::Task<br>
{<br>
... <font color="#80a0ff">// Какие-то внутренности.</font><br>
<font color="#ffff60">public</font>:<br>
... <font color="#80a0ff">// Какой-то конструктор.</font><br>
<br>
<font color="#90f020">void</font> run(Status status) { ... <font color="#80a0ff">/*</font><font color="#80a0ff"> Какие-то действия </font><font color="#80a0ff">*/</font> }<br>
};<br>
...<br>
scheduler.push(std::make_unique<FirstTask>(...));<br>
</font></td>
</tr>
</tbody></table>
<p>предполагалось делать как-то так:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
make_task(scheduler,<br>
[...](Scheduler::Task::Status status) {... <font color="#80a0ff">/*</font><font color="#80a0ff"> Какие-то действия </font><font color="#80a0ff">*/</font> });<br>
</font></td>
</tr>
</tbody></table>
<p>И вспомогательный шаблон функции make_task должен был обернуть лямбду в некий класс-наследник Task. Что-то вроде:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#90f020">template</font><<font color="#90f020">typename</font> H><br>
<font color="#90f020">class</font> LamdaAsTask <font color="#90f020">final</font> : <font color="#ffff60">public</font> Scheduler::Task<br>
{<br>
H _handler;<br>
<br>
<font color="#ffff60">public</font>:<br>
<font color="#90f020">explicit</font> LamdaAsTask(H handler) : _handler{std::move(handler)} {}<br>
<br>
<font color="#90f020">void</font> run(Status condition) <font color="#90f020">override</font><br>
{<br>
_handler(condition);<br>
};<br>
};<br>
<br>
<font color="#90f020">template</font><<font color="#90f020">typename</font> H><br>
<font color="#90f020">void</font> make_task(<br>
Scheduler & scheduler,<br>
H && handler)<br>
{<br>
scheduler.push(std::make_unique<LamdaAsTask<H>>(std::forward<H>(handler)));<br>
}<br>
</font></td>
</tr>
</tbody></table>
<p>Но ситуация была чуть сложнее. Нужно было использовать разные фабрики для Task. Т.е. в make_task передавались не только лямбда и ссылка на Scheduler, но еще и некая фабрика. Именно фабрика брала лямбду и возвращала указатель на новый Task. Т.е. шаблон make_task выглядел так:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#90f020">template</font><<font color="#90f020">typename</font> Factory, <font color="#90f020">typename</font> H><br>
<font color="#90f020">void</font> make_task(<br>
Scheduler & scheduler,<br>
Factory && factory,<br>
H && handler)<br>
{<br>
scheduler.push(factory(std::forward<H>(handler)));<br>
}<br>
</font></td>
</tr>
</tbody></table>
<p>И место для улучшения кода я видел в том, чтобы ограничить параметры шаблона make_task концептами.</p>
<p>Вырисовывалось два концепта.</p>
<p>Первый концепт описывает лямбду-обработчик. Ну типа ее можно вызвать с нужным параметром и она возвращает void.</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#90f020">template</font><<font color="#90f020">typename</font> H><br>
<font color="#90f020">concept</font> Handler_C = <font color="#ffff60">requires</font>(H h)<br>
{<br>
{ h(Scheduler::Task::Status::Normal) } -> std::same_as<<font color="#90f020">void</font>>;<br>
};<br>
</font></td>
</tr>
</tbody></table>
<p>Второй концепт описывает фабрику Task-ов:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#90f020">template</font><<font color="#90f020">typename</font> F, <font color="#90f020">typename</font> H><br>
<font color="#90f020">concept</font> TaskFactory_C = <font color="#ffff60">requires</font>(F f)<br>
{<br>
<font color="#ffff60">requires</font> Handler_C<H>;<br>
<br>
{ f(H{}) } -> std::convertible_to<Scheduler::TaskUniquePtr>;<br>
};<br>
</font></td>
</tr>
</tbody></table>
<p>Т.е. этот концепт зависит от параметра H, который описывает лямбду. Эта лямбда должна удовлетворять требованиям концепта Handler_C. А фабрика сама по себе должна поддерживать вызов operator() с передачей туда лямбды H и возвратом чего-то приводимого к TaskUniquePtr.</p>
<p>Благодаря таким концептам make_task приняла вид:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#90f020">template</font><Handler_C H, TaskFactory_C<H> Factory><br>
<font color="#90f020">void</font> make_task(<br>
Scheduler & scheduler,<br>
Factory && factory,<br>
H && handler)<br>
{<br>
scheduler.push(factory(std::forward<H>(handler)));<br>
}<br>
</font></td>
</tr>
</tbody></table>
<p>В общем, я получил то, что хотел.</p>
<p>Под катом полный код автономного примера, который всю эту кухню демонстрирует.</p>
<p>У меня же пока впечатления от концептов смешанные. Вероятно, вещь хорошая и, местами, полезная. Но вот ВАУ эффекта пока не случилось.</p>
<p>Вообще с C++ есть давняя история: сперва в нем появляется какая-то фича (в смысле описывается в стандарте языка), потом она становится доступна в каком-то из компиляторов, потом у тебя появляется возможность применять ее на практике, потом ты отрефлексируешь свой опыт ее использования и только тогда тебя догоняет понимание того, как же ее нужно использовать, чтобы это было и полезно, и не сложно, и не приводило к проблемам, о которых ты раньше и не знал. С некоторыми фичами эта история растягивается не на один год. Вот как у меня с концептами, например. А до этого похожая история была с noexcept.</p>
<a name='more'></a>
<p>А вот и исходный текст, который можно взять и поэкспериментировать (например, на <a href="https://wandbox.org/permlink/lQuW7B0su2zGjgpN">wandbox-е</a>).</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#ff80ff">#include </font><font color="#ffa0a0"><iostream></font><br>
<font color="#ff80ff">#include </font><font color="#ffa0a0"><memory></font><br>
<font color="#ff80ff">#include </font><font color="#ffa0a0"><utility></font><br>
<br>
<font color="#ff80ff">#include </font><font color="#ffa0a0"><concepts></font><br>
<br>
<font color="#90f020">class</font> Scheduler<br>
{<br>
<font color="#ffff60">public</font>:<br>
<font color="#90f020">class</font> Task<br>
{<br>
<font color="#ffff60">public</font>:<br>
<font color="#90f020">enum</font> <font color="#90f020">class</font> Status { Normal, Deleted };<br>
<br>
<font color="#90f020">virtual</font> ~Task() = <font color="#ffff60">default</font>;<br>
<br>
<font color="#90f020">virtual</font> <font color="#90f020">void</font> run(Status) = <font color="#ffa0a0">0</font>;<br>
};<br>
<br>
<font color="#ffff60">using</font> TaskUniquePtr = std::unique_ptr<Task>;<br>
<br>
<font color="#90f020">virtual</font> <font color="#90f020">void</font> push(TaskUniquePtr task) = <font color="#ffa0a0">0</font>;<br>
};<br>
<br>
<font color="#90f020">template</font><<font color="#90f020">typename</font> H><br>
<font color="#90f020">concept</font> Handler_C = <font color="#ffff60">requires</font>(H h)<br>
{<br>
{ h(Scheduler::Task::Status::Normal) } -> std::same_as<<font color="#90f020">void</font>>;<br>
};<br>
<br>
<font color="#90f020">template</font><<font color="#90f020">typename</font> F, <font color="#90f020">typename</font> H><br>
<font color="#90f020">concept</font> TaskFactory_C = <font color="#ffff60">requires</font>(F f)<br>
{<br>
<font color="#ffff60">requires</font> Handler_C<H>;<br>
<br>
{ f(H{}) } -> std::convertible_to<Scheduler::TaskUniquePtr>;<br>
};<br>
<br>
<font color="#90f020">template</font><Handler_C H><br>
<font color="#90f020">class</font> DummyTask <font color="#90f020">final</font> : <font color="#ffff60">public</font> Scheduler::Task<br>
{<br>
H _handler;<br>
<br>
<font color="#ffff60">public</font>:<br>
<font color="#90f020">explicit</font> DummyTask(H handler) : _handler{std::move(handler)} {}<br>
<br>
<font color="#90f020">void</font> run(Status condition) <font color="#90f020">override</font><br>
{<br>
_handler(condition);<br>
};<br>
};<br>
<br>
<font color="#90f020">class</font> DummyScheduler <font color="#90f020">final</font> : <font color="#ffff60">public</font> Scheduler<br>
{<br>
<font color="#ffff60">public</font>:<br>
<font color="#90f020">void</font> push(TaskUniquePtr task) <font color="#90f020">override</font><br>
{<br>
task->run(Task::Status::Normal);<br>
}<br>
};<br>
<br>
<font color="#90f020">template</font><Handler_C H, TaskFactory_C<H> Factory><br>
<font color="#90f020">void</font> make_task(<br>
Scheduler & scheduler,<br>
Factory && factory,<br>
H && handler)<br>
{<br>
scheduler.push(factory(std::forward<H>(handler)));<br>
}<br>
<br>
<font color="#90f020">struct</font> SimpleFactory<br>
{<br>
<font color="#90f020">template</font><Handler_C H><br>
[[nodiscard]] Scheduler::TaskUniquePtr<br>
<font color="#ffff60">operator</font>()(H && handler)<br>
{<br>
<font color="#ffff60">using</font> TaskType = DummyTask< std::decay_t<H> >;<br>
<font color="#ffff60">return</font> std::make_unique<TaskType>(std::forward<H>(handler));<br>
}<br>
};<br>
<br>
<font color="#90f020">struct</font> NotAFactory<br>
{<br>
<font color="#90f020">template</font><<font color="#90f020">typename</font> H><br>
[[nodiscard]] <font color="#90f020">int</font><br>
<font color="#ffff60">operator</font>()(H && <font color="#80a0ff">/*</font><font color="#80a0ff">handler</font><font color="#80a0ff">*/</font>)<br>
{<br>
<font color="#ffff60">return</font> <font color="#ffa0a0">0</font>;<br>
}<br>
};<br>
<br>
<font color="#90f020">int</font> main()<br>
{<br>
DummyScheduler scheduler;<br>
make_task(<br>
scheduler,<br>
SimpleFactory{},<br>
[](Scheduler::Task::Status <font color="#80a0ff">/*</font><font color="#80a0ff">condition</font><font color="#80a0ff">*/</font>) {<br>
std::cout << <font color="#ffa0a0">"Hello!"</font> << std::endl;<br>
});<br>
<br>
<font color="#ff80ff">#if 0</font><br>
<font color="#80a0ff"> make_task(</font><br>
<font color="#80a0ff"> scheduler,</font><br>
<font color="#80a0ff"> NotAFactory{},</font><br>
<font color="#80a0ff"> [](Scheduler::Task::Status /*condition*/) {</font><br>
<font color="#80a0ff"> std::cout << "Hello!" << std::endl;</font><br>
<font color="#80a0ff"> });</font><br>
<font color="#ff80ff">#endif</font><br>
<br>
<font color="#ff80ff">#if 0</font><br>
<font color="#80a0ff"> make_task(</font><br>
<font color="#80a0ff"> scheduler,</font><br>
<font color="#80a0ff"> SimpleFactory{},</font><br>
<font color="#80a0ff"> [](int /*i*/) { std::cout << "Hello(int)!" << std::endl; });</font><br>
<font color="#ff80ff">#endif</font><br>
}<br>
</font></td>
</tr>
</tbody></table>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com4tag:blogger.com,1999:blog-654279083390275842.post-34884204904569684792024-03-07T14:47:00.000+03:002024-03-07T14:47:24.194+03:00[work.culture] Использование жестких, на грани грубости, оценок в работе<p>Подавляющую часть своей карьеры я работал в отечественных компаниях (не делаю здесь разницы между компаниями из РБ и РФ). И привык к тому, что в нашей производственной культуре нередко используются грубые оценки свершившегося, происходящего и/или грядущего.</p>
<p>Ну т.е. использование фраз вроде "получилось откровенное говно" или "вы предлагаете полную херню" -- это было вполне себе обыденно. И не могу сказать чтобы это сильно людей напрягало. Да, временами это воспринималось персонально, но обычно люди быстро понимали, что здесь нет наезда на них конкретно (а кто не понимал, тот находил себе работу поспокойнее).</p>
<p>Но сейчас идет глобализация, даже мне регулярно приходится переписываться с людьми из разных стран. Нужно заботиться о том, как мои слова будут восприняты. Во-первых, из-за другой культуры. Во-вторых, из-за моего плохого английского. Вроде бы пока никто не жаловался, но тем не менее...</p>
<p>Так вот о глобализации. Она же сказывается. И, есть ощущение, что традиции толерантности проникают и к нам. И уже даже в русскоязычных коллективах начинают использовать эвфемизмы дабы никого не обидеть. Т.е. там, где в середине 90-х просто бы сказали "код херовый", сейчас уже что-то вроде "возможно, в нескольких местах к качеству есть вопросы".</p>
<p>Полагаю, что здесь сказывается не только глобализация и проникновение к нам элементов корпоративной культуры из других стран, но и увеличивающееся с каждым поколением количество "людей-снежинок", ощущающих выгорание от каждого прикосновения к объективной реальности. Ну да не суть.</p>
<p>Суть в том, что лично я бы хотел, чтобы в русскоязычной производственной культуре традиция использования откровенных и жестких (да и местами просто грубых) оценок сохранилась.</p>
<p>Это по типу того, как мы в экстремальных условиях переходим на русский матерный. Ну реально же лучше работает 😉</p>
<p>Правда, тут нужно сделать пару важных оговорок, без которых эта самая производственная культура именно что производственной быть перестанет.</p>
<p>Во-первых, у нас есть ощущение наличия или отсутствия морального права на высказывание оценок. Грубо говоря, если про твой код "херня" говорит человек, который тебя учил программировать или с которым вы прошли не один проект, то это нормально. А когда это делает зеленый новичок или человек со стороны, который пока еще не продемонстрировал понимания предмета, то это чревато конфликтной ситуацией. Иными словами: если вы уже стали "своим" в коллективе, то ваши жесткие высказывания воспринимаются совсем не так, как если вы для коллектива вообще никто, какой-то непонятный хер с горы.</p>
<p>Во-вторых, использование мата в общении. Не буду ханжой и не стану утверждать, что мат вообще не употребляю. Когда случается звиздец, сложно назвать происходящее как-то иначе. Но!</p>
<p>Мат (если без него не обойтись) должен использоваться только в исключительных случаях.</p>
<p>И ни в коем случае нельзя использовать мат в отношении подчиненных. Да, я понимаю, что временами очень тяжело удержаться от вопроса "ты что долбо*б?" или "а не ох*ел ли ты?", но нужно. Да и клиентов, в их отсутствие, так же лучше матами не обкладывать. И не только потому что везде есть уши 🙂</p>
<p>Т.е. если мат используется эпизодически и к месту, то это еще терпимо. Хотя лучше бы вообще без... Но жизнь есть жизнь.</p>
<p>А вот когда мат используется для постоянного общения... Вот это плохо. Особенно когда это делают начальники в присутствии подчиненных. Это уже просто мат ради мата, к прямоте и откровенности это не имеет никакого отношения.</p>
<p>И да, есть еще и, в-третьих: если был не прав, то это нужно публично признавать. Это еще одна составляющая той самой прямоты и откровенности.</p>
<hr/>
<p>В общем, к чему это я?</p>
<p>К тому, что хотелось бы иметь возможность прямо спросить "почему здесь сделано через жопу?" и услышать в ответ "времени было мало, успели только через жопу". Прямота и откровенность лучше попыток прикрыть горькую правду словесной эквилибристикой.</p>
<p>Ведь речь идет о "производственной" культуре, а не о культуре вообще. А в производстве прямота и откровенность, имхо, один из основных факторов для выживания этого самого производства.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com2tag:blogger.com,1999:blog-654279083390275842.post-54379471416135735882024-03-04T09:39:00.000+03:002024-03-04T09:39:40.455+03:00[prog.flame] Пример серьезного, на мой взгляд, просчета в API библиотеки libconfig<p>Впервые довелось столкнуться с библиотекой <a href="https://github.com/hyperrealm/libconfig">libconfig</a>. Довольно таки популярной, насколько я могу судить. Тем удивительнее было обнаружить там описанный ниже косяк.</p>
<p>Чтобы получить значение целочисленного параметра нужно использовать функцию config_setting_get_int:</p>
<blockquote>
<code><pre>int config_setting_get_int (const config_setting_t * setting)</pre></code>
<code><pre>long long config_setting_get_int64 (const config_setting_t * setting)</pre></code>
<p>These functions return the value of the given setting. If the type of the setting does not match the type requested, a 0 value is returned.</p>
</blockquote>
<p>Т.е. если мы пытаемся получить значение параметра, а нам возвращают 0, то этот ноль может означать:</p>
<ul>
<li>что значение не получено, т.к. оно имеет другой тип. Например, вместо <tt>my_param=0</tt> задано <tt>my_param="0"</tt> или <tt>my_param="zero"</tt>;</li>
<li>что значение получено и это таки ноль. Просто ноль.</li>
</ul>
<p>Получается, что если для нашего параметра ноль -- это допустимое значение, то получив ноль из config_setting_get_int мы не знаем, ноль -- это ноль, или же это признак неправильного значения в конфиге.</p>
<p>Аналогичная проблема есть и с семейством функций config_lookup_int/config_lookup_int64 и прочих вариантов lookup-чего-то-там. Эти функции возвращают CONFIG_FALSE и в случае, если параметр вообще не был найдет, и в случае, если был найден, но содержит не тот тип (например, строка вместо числа).</p>
<p>При этом, насколько я могу судить по коду libconfig, в случае несовпадения типа для значения libconfig даже errno не меняет.</p>
<p>Т.е. получив 0 из config_setting_get_int или CONFIG_FALSE из config_lookup_int у меня нет возможности разобраться с тем есть ли ошибка и, если есть, какая она.</p>
<p>Хотя, как по мне, избежать этой проблемы можно было бы очень просто, если бы у config_setting_get_int был другой формат:</p>
<code><pre>int config_setting_get_int(
// Где искать значение.
const config_setting_t * setting,
// Куда помещать значение.
int * value_receiver)</pre></code>
<p>И возвращаемое значение означало бы признак успешности, вроде такого: 0 -- все OK, -1 -- значение имеет другой тип, -2 -- значение слишком большое, чтобы уместиться в int и т.д.</p>
<p>Очевидная, вроде бы, вещь. Но почему-то не сделанная... 🙁</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com2tag:blogger.com,1999:blog-654279083390275842.post-78172080311898724472024-03-02T10:48:00.000+03:002024-03-02T10:48:15.950+03:00[life.cinema] Очередной кинообзор (2024/02)<p>Подошло время очередного отчета о просмотренных за минувший месяц фильмах. Традиционно в начале каждого из списков идет то, что понравилось больше, а в конце -- то, на что можно не тратить свое время. Сразу спойлер: ничего реально достойного нет (фильм "Звук свободы" идет своей отдельной категорией) :(</p>
<p><b>Фильмы</b></p>
<p><a href="https://www.kinopoisk.ru/film/4626783/">Пчеловод (The Beekeeper, 2024)</a>. В описании фильма забыли написать, что это фантастический боевик :) Иногда, чтобы подчеркнуть чью-то крутизну, говорят, что "круче только горы и вареные яйца". Но нет таких гор, которые были бы круче, чем герой Стейтема в этом фильме :))) Посмотреть вполне можно, экшОн снят бодренько. Но ни в коем случае нельзя относиться к происходящему на экране всерьез.</p>
<p><a href="https://www.kinopoisk.ru/film/4859964/">Плохие парни (Bad Hombres, 2023)</a>. Откровенно бюджетный фильм, но за развитием событий в котором интересно следить. Могло бы получиться на удивление неплохо если бы откровенно унылым финалом авторы не спустили все свои предыдущие усилия в унитаз.</p>
<p><a href="https://www.kinopoisk.ru/film/4947994/">Территория зла (Land of Bad, 2024)</a>. Очень двойственные впечатления: с одной стороны картинка шикарная. Но с другой постоянно преследует ощущение "нам втирают какую-то дичь". По итогу ощущения негативные, можно пройти мимо этого фильма.</p>
<p><b>Сериалы</b></p>
<p><a href="https://www.kinopoisk.ru/series/4498603/">Месье Спейд (Monsieur Spade, первый сезон, 2024)</a>. Очень красиво снято. И Клайв Оуэн, как по мне, отлично воплотил образ частного сыщика из детективов Дешела Хэммита. Но в целом сериал оставил посредственные впечатления. Слишком много внезапных флешбэков, слишком много розовых соплей, слишком неспешное повествование на протяжении всего сериала, но при этом очень скомканные и кульминация с развякой (толком не понимаешь что же именно и из-за чего произошло).</p>
<p><a href="https://www.kinopoisk.ru/series/4912742/">Кошка (первый сезон, 2023)</a>. Ну такое себе. Мне гораздо интереснее было смотреть на виды Калининграда, в котором события развивались, чем за самими событиями. Так что не советую.</p>
<p><a href="https://www.kinopoisk.ru/series/1209839/">Джек Ричер (Reacher, второй сезон, 2023)</a>. Первые семь серий были вполне себе в духе первого сезона. Но в последней серии авторы устроили такую муть, что убили вообще все положительные впечатления. В итоге сильно не понравилось.</p>
<p><a href="https://www.kinopoisk.ru/series/5368058/">Иные (первый сезон, 2023)</a>. Похоже, что все деньги и усилия были вложены в визуальную составляющую. Эта часть, действительно, выглядит достойно. Чего не скажешь о всем остальном. Так что можно смело проходить мимо.</p>
<p><a href="https://www.kinopoisk.ru/series/681831/">Настоящий детектив (True Detective, четвертный сезон, 2024)</a>. Это не детектив, а редкой нудятины муть про страдания "сильных и независимых женщин" (c), да еще и чрезмерно сдобренная мистикой. Категорически не рекомендую.</p>
<p><b>Фильм вне категории</b></p>
<p><a href="https://www.kinopoisk.ru/film/1134493/">Звук свободы (Sound of Freedom, 2023)</a>. История, рассказанная в фильме поражает и, конечно же, не может оставить равнодушным. Но вот сам фильм, именно как фильм, мне показался откровенно слабыми и не раскрывающим весь ужас данной темы. Поэтому как-то оценивать его в целом не берусь.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0tag:blogger.com,1999:blog-654279083390275842.post-32723398686230068442024-02-29T07:30:00.000+03:002024-02-29T07:30:12.963+03:00[prog.c++.flame] Вот пример того, что мне сильно не нравится в C++<p>Попалась мне несколько дней назад старая статья на тему хитрых трюков в C++: "<a href="https://b.atch.se/posts/non-constant-constant-expressions/">NON-CONSTANT CONSTANT-EXPRESSIONS IN C++</a>". Да, я знаю, статья не новая, но вот такой я медленный газ... 🙁</p>
<p>В статье показывается некий трюк, который типа должен позволить сделать так:</p>
<code><pre>int main () {
constexpr int a = f ();
constexpr int b = f ();
static_assert (a != b, "fail");
}</pre></code>
<p>Т.е. два вызова одной и той же constexpr-функции в compile-time должны дать разный результат.</p>
<p>Мне потребовалось несколько подходов к этой статье, чтобы понять что там написано и как предложенное решение работает. В итого, вроде бы, понял всю эту магию. Но точно могу сказать, что повторить это самостоятельно не смогу.</p>
<p>Посему захотелось написать о том, как же я не люблю эту сторону C++, когда люди находят хитрую лазейку в правилах языка и вместо того, чтобы сказать "вот здесь есть лазейка, которая может привести к неожиданным результатам, будьте осторожны, а еще лучше давайте придумаем как ее исправить и/или вообще устранить", они начинают писать статьи о том, как эту лазейку можно эксплуатировать.</p>
<p>Хочется привести такую аналогию: возможно вы замечали, что временами при вставке штепселя в электрическую розетку возникает искра (и при извлечении такое случается). Иногда искр меньше, иногда больше. Насколько я знаю, этот эффект физиками давно изучен и он лежит в основе электроискровой резки металлов. Ну да не суть. Суть в том, что вот кто-то обнаружил этот эффект и начал экспериментировать: штепсели с разным диаметром контактов, скорость вставки/извлечения, угол под которым все это выполняется и т.д. А потом написал статью, что типа если вы будете делать вот так, то у вас гарантированно будут искры и искры эти будут наиболее яркими. А потом кто-нибудь еще и попытается извлечь какую-нибудь "пользу" из такого "открытия"?</p>
<p>Нормальные люди прочитав о таком, скорее всего, просто покрутят пальцем у виска.</p>
<p>Тогда как в мире C++ вот такое вот "открытие" привлекает внимание и ведет к продолжению подобных поисков и экспериментов. И это считается нормальным. Более того, такие вещи обсуждаются. Возможно, кто-то даже решается применить что-то подобное в своей работе. А потом это вырастет в какую-то продвинутую технику программирования на C++, по типу случайно открытого в середине 1990-х метапрограммирования не шаблонах...</p>
<p>Мне это не нравится. Все-таки хочется, чтобы С++ развивался в сторону удобного, практичного и предсказуемого инструмента, где для решения задач используются специально спроектированные возможности языка программирования, а не это вот все 🙁</p>
<p>Но самый цимес обнаружился при попытке проверить представленное в статье решение 😅</p>
<p>Оказалось, что само решение (в коротком его варианте), работает только в VC++.</p>
<p>Под GCC работает только то длинное решение, которое типа должно обходить баг в clang-е. А вот короткое решение не работает.</p>
<p>Тогда как в clang-е не работает вообще ни короткое, ни длинное.</p>
<p>Ну и, спрашивается, нахера весь этот огород было городить, если он "работает" только на одном из компиляторов? Так и хочется вспомнить старое, но актуальное: "Не <strike>выёбывай</strike> умничай" 🤬</p>
<p>Павбывавбы.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com2tag:blogger.com,1999:blog-654279083390275842.post-71277277763081999192024-02-12T14:17:00.002+03:002024-02-12T15:53:37.220+03:00[prog.multithreading.bugs] Повезло столкнуться с собственным багом в многопоточном коде<p>В конце прошлой недели убил почти два часа чтобы найти и исправить баг в многопоточном коде. В мною написанном и, как казалось, протестированном и отлаженном коде.</p>
<p>Код делал относительно несложную штуку: формировал очередь запросов, поступающих из разных рабочих нитей и ждущих разрешения на выполнение. Какие-то из этих запросов могли быть запущены в параллель, какие-то должны были ждать завершения ранее начатых запросов, какие-то должны были уходить в конец очереди при появлении более приоритетных запросов.</p>
<p>Но, как оказалось, не всегда это выполнялось правильно. Даже не смотря на наличие тестов 🙁</p>
<p>Особо доставили два момента:</p>
<p>Во-первых, обнаружение бага. Чистая случайность. Делал очередной прогон приложения со включенными отладочными печатями и буквально краем глаза заметил что-то необычное в консоли. Там было несколько дампов с перечнем работающих и ждущих запросов. И в части этих дампов указывалось, что с какого-то момента в работу пошло вообще все, а очередь опустела, хотя часть запросов должна была бы все еще оставаться в очереди.</p>
<p>Во-вторых, осознание того, что я не помню последовательности запросов, которая привела к такой ситуации. Я их накидывал случайно, в разном порядке, с разными параметрами. И когда заметил подозрительные следы в отладочных печатях, то не смог вспомнить в каком именно порядке какие запросы выдавались.</p>
<p>Так что внезапно обнаружил себя в ситуации, когда баг явно есть, но как он возник решительно непонятно. Как и непонятно есть ли вообще возможность его воспроизвести (и во что все это выльется).</p>
<p>Пришлось чуть ли не в буквальном смысле "курить бамбук": смотреть в код, смотреть в получившиеся отладочные печати, опять смотреть в код, опять смотреть в отладочные печати, опять смотреть в код... Все время пытаясь понять "а вот если оно пошло вот по этой ветке, то...", периодически отвлекаясь на попытки подумать о том, а можно ли сделать тестовый прогон, который бы подтвердил или опроверг очередную гипотезу.</p>
<p>В какой-то момент мозг начал закипать. В общем-то, два часа на поиск бага в многопоточном коде -- это не много, но когда эти два часа ты можешь разве что листать код вперед назад и рисовать схемки на бумаге, то это долго 😉</p>
<p>Оказалось вот что: у меня был ассоциативный контейнер (std::map), содержимое которого защищалось мутексом. Но в одной из веток происходило следующее:</p>
<ul>
<li>захваченный мутекс отпускался чтобы дать другим нитям возможность обратиться к этому контейнеру;</li>
<li>текущая нить (которая ранее владела мутексом) засыпала в ожидании некого события;
<li>когда это самое событие происходило, текущая нить просыпалась и вносила изменения в этот контейнер.</li>
</ul>
<p>По недосмотру в коде не оказалось повторного захвата мутекса после того, как текущая нить дождалась своего события и проснулась. Поэтому обновление контейнера было уже не thread-safe 🥴</p>
<p>Поэтому получилось, как я полагаю, следующее: на ожидании своих событий заснуло две нити. Потом они проснулись практически одновременно одна за одной (вот такое вот счастливое для меня стечение обстоятельств) и сперва первая добавила свою информацию в этот контейнер, а потом и вторая. Причем, подозреваю, обновление контейнера происходило чуть ли не одновременно. Т.к. стечение обстоятельств было действительно счастливым, то изначально контейнер был пустым и сперва одна нить туда добавила свою информацию, а затем и вторая. При этом, как я подозреваю, вторая нить просто полностью перезаписала содержимое контейнера. Из-за чего в нем оказался один-единственный элемент, а не два. А уже это привело к нарушению всей последующей логики обработки очереди запросов и в работу ушли даже те запросы, которые должны были еще подождать.</p>
<p>В общем, целый ряд счастливых случайностей:</p>
<ul>
<li>сперва я очень удачно сгенерировал "правильную" последовательность запросов которая привела к тому, что две рабочие нити проснулись в одно время;
<li>затем повезло с тем, что при перезаписи std::map-а из разных потоков не образовался какой-то мусор из-за чего бы программа могла бы упасть с segmentation fault;</li>
<li>и все это случилось когда в программе еще оставались отладочные печати, благодаря которым на консоль сбрасывались дампы с информацией о текущих запросах;</li>
<li>ну и каким-то чудом в этих самых дампах я заметил то, что у ряда запросов статус оказался "в работе", а не "в ожидании".</li>
</ul>
<p>Короче говоря, без везения в поиске багов в многопоточке не обойтись 😎</p>
<p>А в завершении хочется повторить то, что я уже неоднократно говорил (и буду делать это снова и снова): многопоточность -- это пот, боль и кровь. Посему если у вас есть возможность не писать многопоточный код, то не пишите его.</p>
<p>Сам я себя ни в коем случае специалистом по многопоточному программированию не считаю, мне тупо не хватает мозгов, чтобы моделировать все то многообразие сочетаний событий, которое может возникнуть в многопоточном коде. Я поэтому-то SObjectizer-ом и занимаюсь, чтобы свести работу с многопоточностью к минимуму. Поэтому в моем многопоточном коде баги были, есть и будут. Куда же без них 😉 Главное, чтобы они вовремя наружу вылазили, под присмотром 🤣</p>
<hr/>
<p>На правах саморекламы: <a href="https://eao197.blogspot.com/2023/09/work-c.html">изобретаю велосипеды для себя, могу изобретать и для вас</a>.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com3tag:blogger.com,1999:blog-654279083390275842.post-81796065278126129642024-02-10T11:34:00.002+03:002024-02-11T08:51:14.253+03:00[prog.multithreading] Нужна помощь в поиске названия для примитива синхронизации, похожего на std::latch<p>Мне тут потребовался примитив синхронизации, в чем-то похожий на добавленный в C++20 <a href="https://en.cppreference.com/w/cpp/thread/latch">std::latch</a>. Но с важным отличием: в `std::latch` нужно в конструкторе указывать значение счетчика. А в моем случае это количество заранее точно неизвестно.</p>
<p>Грубо говоря, сценарий использования `std::latch`: есть тред A, который ждет, пока N тредов B(i) сделают кусок работы. Тред A засыпает на `wait`, каждый тред B(i) рано или поздно вызывает `count_down` и когда это сделают все треды B(i), тред А проснется.</p>
<p>Все это отлично работает пока N известно заранее.</p>
<p>В моем же случае тред С создает сперва тред A, а затем начинает создавать треды B. И тред A точно не знает, сколько именно C создаст тредов B. Просто в какой-то момент треду A нужно будет дождаться пока запущенные треды B завершат свою работу. Для чего каждый тред B сперва инкрементирует счетчик, а затем декрементирует. Треду же А достаточно дождаться обнуления этого счетчика.</p>
<p>Сделанный для этих целей простой вариант "барьера" можно увидеть под катом.</p>
<p>Используется приблизительно следующим образом:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#80a0ff">// Это все внутри треда C.</font><br>
meeting_room_t completion_room;<br>
<br>
std::thread thread_a{[&]() {<br>
... <font color="#80a0ff">// что-то делает.</font><br>
<font color="#80a0ff">// Нужно дождаться пока треды B завершат свою работу.</font><br>
completion_room.wait_then_close();<br>
... <font color="#80a0ff">// еще что-то делает.</font><br>
}};<br>
<br>
<font color="#80a0ff">// Создаем треды B.</font><br>
std::vector<std::thread> threads_b;<br>
<font color="#ffff60">while</font>(some_condition()) {<br>
threads_b.push_back([&completion_room]() {<br>
completion_room.enter(); <font color="#80a0ff">// Увеличили счетчик.</font><br>
... <font color="#80a0ff">// что-то делает.</font><br>
completion_room.leave(); <font color="#80a0ff">// Уменьшили счетчик.</font><br>
});<br>
... <font color="#80a0ff">// какие-то еще действия.</font><br>
}<br>
<br>
<font color="#80a0ff">// Осталось дождаться завершения работы.</font><br>
<font color="#ffff60">for</font>(<font color="#90f020">auto</font> & t : threads_b) t.join();<br>
thread_a.join();<br>
</font></td>
</tr>
</tbody></table>
<p>Возникла сложность с названием для такого примитива синхронизации.</p>
<p>Пока что у меня есть такая аналогия: комната для совещаний в офисе. В течении рабочего дня ее могут занимать для проведения совещаний, а в конце рабочего дня ее нужно закрыть. Но закрывать можно только когда в ней никого нет. Если же в комнате совещание идет, то участники могут туда заходить и выходить оттуда: пока там есть хотя бы один человек, то совещание считается незавершенным и закрывать комнату нельзя. Когда же все участники совещания комнату покинули, то считается, что совещание закончено и комнату можно закрыть.</p>
<p>Поэтому пока в качестве рабочего названия используется meeting_room. Однако, есть ощущение, что название не самое удачное. Вот и пытаюсь воспользоваться чужой помощью, чтобы придумать что-то получше.</p>
<p><i><b>Upd</b>. Похоже, что такая штука назвается rundown: <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/run-down-protection">Run-Down Protection</a>. Большое спасибо <a href="https://www.linkedin.com/in/konstantin-konst-sharon-96276b1">Константину</a> за наводку.</i></p>
<p>Текущая реализация meeting_room_t:</p>
<a name='more'></a>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#90f020">class</font> meeting_room_t {<br>
std::mutex lock_;<br>
std::condition_variable wakeup_cv_;<br>
<br>
<font color="#90f020">bool</font> closed_{<font color="#ffa0a0">false</font>};<br>
<font color="#90f020">unsigned</font> attenders_{};<br>
<br>
<font color="#ffff60">public</font>:<br>
meeting_room_t() = <font color="#ffff60">default</font>;<br>
<br>
<font color="#90f020">void</font> enter() {<br>
std::lock_guard<std::mutex> lock{lock_};<br>
<font color="#ffff60">if</font>(closed_)<br>
<font color="#ffff60">throw</font> std::runtime_error{<font color="#ffa0a0">"meeting_room is closed"</font>};<br>
++attenders_;<br>
}<br>
<br>
<font color="#90f020">void</font> leave() <font color="#ffff60">noexcept</font> {<br>
std::lock_guard<std::mutex> lock{lock_};<br>
--attenders_;<br>
<font color="#ffff60">if</font>(!attenders_)<br>
wakeup_cv_.notify_all();<br>
}<br>
<br>
<font color="#90f020">void</font> wait_then_close() {<br>
std::unique_lock<std::mutex> lock{lock_};<br>
<font color="#ffff60">if</font>(attenders_)<br>
{<br>
wakeup_cv_.wait(lock, [<font color="#ffff60">this</font>]{ <font color="#ffff60">return</font> <font color="#ffa0a0">0u</font> == attenders_; });<br>
closed_ = <font color="#ffa0a0">true</font>;<br>
}<br>
}<br>
};<br>
</font></td>
</tr>
</tbody></table>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com6tag:blogger.com,1999:blog-654279083390275842.post-22566810192165266752024-02-06T15:22:00.002+03:002024-02-07T07:49:35.764+03:00[prog.c++.kill'em-all] C++ный код, от которого у меня изрядно подгорает<p>Давно программирую, видел всякое, ко многим проявлениям программерского дебилизма смог привыкнуть. Но есть образчики кода, которые неизбежно вызывают у меня лютый баттхерт. Например, что-то вот такое:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
error_code use_resource(resource_id res_id) {<br>
<font color="#ffff60">if</font>(<font color="#90f020">auto</font> r = first_operation(res_id); r != error_code::ok) {<br>
dispose(res_id);<br>
<font color="#ffff60">return</font> r;<br>
}<br>
<font color="#ffff60">if</font>(<font color="#90f020">auto</font> r = second_operation(res_id); r != error_code::ok) {<br>
dispose(res_id);<br>
<font color="#ffff60">return</font> r;<br>
}<br>
...<br>
dispose(res_id);<br>
<font color="#ffff60">return</font> error_code::ok;<br>
}<br>
</font></td>
</tr>
</tbody></table>
<p>Думаю, что несложно догадаться, что именно триггерит: это обилие вызовов dispose.</p>
<p>Я могу простить тот факт, что в use_resource передается голый дескриптор ресурса, а не какая-то RAII-обертка вокруг него.</p>
<p>Ну мало ли, бывает. Может эта функция вообще как extern "C" описана и предназначена для того, чтобы ее вызывали из Си-шного кода. Или же это часть древнего проекта и первоначально use_resource была написана еще в конце 1980-х, а сейчас ее просто дорабатывают не имея возможности поменять все 100500 мест в старой кодовой базе, где она вызывается именно вот так.</p>
<p>Но блин, почему нельзя сделать RAII обертку уже внутри use_resource?</p>
<p>Хотя бы подобным образом:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
error_code use_resource(resource_id res_id) {<br>
<font color="#90f020">struct</font> resource_disposer {<br>
resource_id m_id;<br>
resource_disposer(resource_id id) : m_id(id) {}<br>
~resource_disposer() { dispose(m_id); }<br>
} disposer(res_id);<br>
<br>
<font color="#ffff60">if</font>(<font color="#90f020">auto</font> r = first_operation(res_id); r != error_code::ok) {<br>
<font color="#ffff60">return</font> r;<br>
}<br>
<font color="#ffff60">if</font>(<font color="#90f020">auto</font> r = second_operation(res_id); r != error_code::ok) {<br>
<font color="#ffff60">return</font> r;<br>
}<br>
...<br>
<font color="#ffff60">return</font> error_code::ok;<br>
}<br>
</font></td>
</tr>
</tbody></table>
<p>Причем реализация такого `resource_disposer` -- это вообще C++98. Таким подходом можно пользоваться уже больше двадцати пяти(!!!) лет без оглядки на версию компилятора. В современном C++ можно было бы найти еще несколько способов достижения той же самой цели (хотя бы <a href="https://github.com/microsoft/GSL/blob/f1a494cfd2ce55fe88b5134eab985f5852667b8d/include/gsl/util#L64-L92">finally из GSL</a>), более лаконичных.</p>
<p>На эту тему подобной "очистки" ресурсов я уже неоднократно писал. Вот, например, от 2015-го года (уже почти десять лет как!!!): <a href="https://eao197.blogspot.com/2015/10/progc.html">раз</a> и <a href="https://eao197.blogspot.com/2015/10/progc_15.html">два</a>. Но, как я смотрю, время идет, а <strike>криворуких программистов</strike> недоучек меньше не становится.</a></p>
<p>И да, я злой, т.к. считаю, что подобный код является признаком профнепригодности, т.к. человек не видит очевидных моментов с дублированием одной и той же функциональности.</p>
<p>Вероятно, C++ программистов нужно начинать учить с идиомы RAII. А уже все остальное -- потом.</p>
<p>Ну а Си-программистов, по аналогии, нужно начинать учить с идиомы goto err (или goto cleanup). Даже не смотря на то, что goto -- это зло. Как и чистый Си, впрочем ;)</p>
<hr/>
<p>На правах саморекламы: <a href="https://eao197.blogspot.com/2023/09/work-c.html">изобретаю велосипеды для себя, могу изобретать и для вас</a>.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com4tag:blogger.com,1999:blog-654279083390275842.post-8031094530880790402024-02-05T12:01:00.000+03:002024-02-05T12:01:46.106+03:00[prog.c++] Захотелось в C++ странного (на тему транзитивной константности)...<p>Недавно столкнулся с задачей, в которой было бы хорошо иметь транзитивную константность в C++.</p>
<p>Транзитивная константность -- штука своеобразная. И, по большей части, мне нравится, что в C++ ее нет. Но вот оказался в ситуации, когда она была бы в тему.</p>
<p>Что такое транзитивная константность?</p>
<p>Представим себе, что у нас есть:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#60ff60">class</font> Foo {<br>
<font color="#ffff60">public</font>:<br>
<font color="#60ff60">void</font> foo(); <font color="#80a0ff">// Это не-const метод.</font><br>
<font color="#60ff60">void</font> foo2() <font color="#60ff60">const</font>; <font color="#80a0ff">// А это уже const-метод.</font><br>
};<br>
<br>
<font color="#60ff60">class</font> Bar {<br>
Foo * m_foo; <font color="#80a0ff">// Это не-const указатель!</font><br>
<font color="#ffff60">public</font>:<br>
...<br>
<font color="#60ff60">void</font> bar2() <font color="#60ff60">const</font> { <font color="#80a0ff">// Это const-метод, в котором мы не можем присвоить m_foo новое значение.</font><br>
m_foo->foo(); <font color="#80a0ff">// Но зато можем вызвать не-const метод для m_foo.</font><br>
}<br>
};<br>
<br>
Foo foo;<br>
<font color="#60ff60">const</font> Bar bar{&foo};<br>
bar.bar2(); <font color="#80a0ff">// Этот вызов может изменить foo.</font><br>
</font></td>
</tr>
</tbody></table>
<p>Из-за того, что в C++ константность не транзитивна, то в const-объекте bar можно иметь не-const указатель на foo и в const-методе Bar::bar2 можно поменять объект foo.</p>
<p>Если бы константность была транзитивной, то в Bar::bar2 указатель Bar::m_foo автоматически бы стал константным и вызвать в Bar::bar2 не-const метод Foo::foo у нас уже не получилось бы.</p>
<p>Поскольку в С++ транзитивной константности нет, то я было попробовал сделать ее вручную. По типу чего-то такого:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#60ff60">template</font><<font color="#60ff60">typename</font> T><br>
<font color="#60ff60">class</font> AutoConstPtr {<br>
T * m_ptr;<br>
<font color="#ffff60">public</font>:<br>
...<br>
[[nodiscard]] T * get() { <font color="#ffff60">return</font> m_ptr; } <font color="#80a0ff">// Не-const.</font><br>
[[nodiscard]] <font color="#60ff60">const</font> T * get() <font color="#60ff60">const</font> { <font color="#ffff60">return</font> m_ptr; } <font color="#80a0ff">// Уже const.</font><br>
};<br>
</font></td>
</tr>
</tbody></table>
<p>Это позволяет получить транзитивную константность в простом случае:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#60ff60">class</font> Bar {<br>
AutoConstPtr<Foo> m_foo; <font color="#80a0ff">// Это уже не raw pointer.</font><br>
<font color="#ffff60">public</font>:<br>
...<br>
<font color="#60ff60">void</font> bar2() <font color="#60ff60">const</font> {<br>
m_foo.get()->foo(); <font color="#80a0ff">// А вот здесь будет ошибка компиляции!</font><br>
}<br>
};<br>
</font></td>
</tr>
</tbody></table>
<p>И это уже было именно то, что мне нужно. И, казалось бы, счастье было уже так близко...</p>
<p>Но, к сожалению, это не сработало на практике. Например, из-за вот таких случаев:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#60ff60">void</font> ProcessItems(<font color="#60ff60">const</font> std::vector<AutoConstPtr<Foo>> & items) {<br>
<font color="#ffff60">for</font>(<font color="#60ff60">auto</font> p : items) {<br>
p.get()->foo(); <font color="#80a0ff">// Упс!</font><br>
}<br>
}<br>
</font></td>
</tr>
</tbody></table>
<p>Фокус в том, что p -- это будет копия AutoConstPtr<Foo>. Не-const копия. Следовательно, для p будет вызываться не-const версия get. Следовательно, будет возвращаться не-const указатель на Foo. Следовательно, можно вызывать не-const методы Foo, т.е. модифицировать Foo. И это в ситуации, когда исходно у нас были как раз константные указатели на Foo (ведь у нас const-ссылка на вектор указателей).</p>
<p>Вот таким вот незамысловатым образом красивая идея накрылась медным тазом. Абыдна, да 🙁</p>
<p>И вот тут мне захотелось, чтобы в C++ при описании конструктора можно было бы явно описать, применяется ли этот конструктор для const-объекта или нет.</p>
<p>Ведь сейчас мы пишем что-то вроде:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
MyClass::MyClass(<font color="#60ff60">const</font> MyClass & other) {...}<br>
</font></td>
</tr>
</tbody></table>
<p>и понимаем, что это конструктор копирования. Но не понимаем, какой именно экземпляр MyClass при этом конструируется. Т.е.:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
MyClass source{...};<br>
<br>
MyClass copy1{source}; <font color="#80a0ff">// Вызов конструктора копирования.</font><br>
<font color="#60ff60">const</font> MyClass copy2{source}; <font color="#80a0ff">// Вызов того же самого конструктора копирования.</font><br>
</font></td>
</tr>
</tbody></table>
<p>А что, если бы мы могли добавлять const и к конструктору?</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#60ff60">template</font><<font color="#60ff60">typename</font> T><br>
<font color="#60ff60">class</font> AutoConstPtr {<br>
T * m_ptr;<br>
<font color="#ffff60">public</font>:<br>
... <font color="#80a0ff">// Здесь какие-то другие конструкторы.</font><br>
AutoConstPtr(<font color="#60ff60">const</font> AutoConstPtr &) = <font color="#ffff60">delete</font>; <font color="#80a0ff">// Нельзя построить не-const из const.</font><br>
AutoConstPtr(<font color="#60ff60">const</font> AutoConstPtr & other) <font color="#60ff60">const</font> <font color="#80a0ff">// Тут все OK.</font><br>
: m_ptr{other.m_ptr}<br>
{}<br>
...<br>
};<br>
</font></td>
</tr>
</tbody></table>
<p>Тогда бы не получилось бы скомпилировать конструкцию:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#ffff60">for</font>(<font color="#60ff60">auto</font> p : items) ...<br>
</font></td>
</tr>
</tbody></table>
<p>потому что нельзя построить не-const объект AutoConstPtr из const-объекта.</p>
<p>А вот так бы получилось бы:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#ffff60">for</font>(<font color="#60ff60">const</font> <font color="#60ff60">auto</font> p : items) ...<br>
</font></td>
</tr>
</tbody></table>
<hr/>
<p>Вот такая вот странная фантазия.</p>
<p>Но это реально фантазия, т.к. даже если бы была возможность помечать конструкторы как const, то непонятно было бы что делать вот с такими ситуациями:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#60ff60">const</font> <font color="#60ff60">auto</font> std::vector<AutoConstPtr<Foo>> & source = ...;<br>
std::vector<AutoConstPtr<Foo>> selected;<br>
std::copy_if(source.begin(), source.end(),<br>
std::back_inserter(selected),<br>
[](<font color="#60ff60">const</font> <font color="#60ff60">auto</font> & item) { <font color="#ffff60">return</font> ...; });<br>
</font></td>
</tr>
</tbody></table>
<p>Так что, возможно, идея транзитивной константности в принципе не для C++.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0tag:blogger.com,1999:blog-654279083390275842.post-81560316409544745842024-02-01T19:10:00.000+03:002024-02-01T19:10:02.289+03:00[life.cinema] Очередной кинообзор<p>Подошло время очередного кинообзора. Как обычно, в начале каждого из списков идут фильмы, которые понравились больше, а в конце то, на что можно не тратить свое время.</p>
<p><b>Фильмы</b></p>
<p><a href="https://www.kinopoisk.ru/film/2046399/">День мертвых (2021)</a>. Отличный пример кино, в котором минимум персонажей, минимум событий, а все держится на разговорах, но при этом следить за происходящим интересно. Хотя, наверняка, зайдет этот фильм далеко не всем.</p>
<p><a href="https://www.kinopoisk.ru/film/4745702/">Общество снега (La sociedad de la nieve, 2023)</a>. Впечатляющая история, снято все красиво, подача материала необычная... Но вот чего-то мне сильно не хватило. Не шедевр, к сожалению. А очень жаль. Тем не менее, имеет смысл смотреть.</p>
<p><a href="https://www.kinopoisk.ru/film/5107088/">Семейный план (The Family Plan, 2023)</a>. На удивление неплохо. Вполне можно посмотреть когда хочется отключить мозги и развлечься.</p>
<p><a href="https://www.kinopoisk.ru/film/4922959/">Догмен (Dogman, 2023)</a>. Если смотреть на этот фильм как на фэнтезино-фантастическую картину, вроде "Джокера", то в рамках этого жанра еще ничего, вполне смотрибельно.</p>
<p><a href="https://www.kinopoisk.ru/film/602675/">Каменщик (The Bricklayer, 2023)</a>. Ну такое себе, на троечку. Хотя есть там что-то от духа боевиков 1980-х годов.</p>
<p><a href="https://www.kinopoisk.ru/film/4472264/">Дворец (The Palace, 2023)</a>. Не смог толком оценить. Вроде бы снято круто, вроде бы все закручивается и закручивается и в финале должен случиться апупей с апупеозом... Но заканчивается кино каким-то невнятным пшиком.</p>
<p><a href="https://www.kinopoisk.ru/film/1236072/">Озеро диких гусей (Nan fang che zhan de ju hui, 2019)</a>. Сам фильм мне не зашел, но в чем-то это оказалась любопытная картина, т.к. очень уж сильно отличается от европейского, не говоря уже про американское, кино.</p>
<p><a href="https://www.kinopoisk.ru/film/4538172/">Мятежная Луна, часть 1: Дитя огня (Rebel Moon - Part One: A Child of Fire, 2023)</a>. Попытка Netflix-а заполучить свою фэнтезийно-космическую франшизу. Получилась редкая муть (что-то по типу <a href="https://www.kinopoisk.ru/film/505966/">Восхождение Юпитер</a>). Только, в отличии от "Восхождения Юпитер" здесь, помимо всего прочего, меня еще и визуальная составляющая раздражала (как и <a href="https://www.kinopoisk.ru/film/428683/">в предыдущей большой работе Зака Снайдера</a>).</p>
<p><b>Сериалы</b></p>
<p><a href="https://www.kinopoisk.ru/series/1331649/">Медленные лошади (Slow Horses, третий сезон, 2023)</a>. Бодренько и динамично. Мне понравилось.</p>
<p><a href="https://www.kinopoisk.ru/series/1177527/">Тень за спиной (первый сезон, 2018)</a>. На удивление неплохо, смотреть было интересно. Хотя основной твист мы с супругой, так уж получилось, сразу разгадали. Ну и в самом фильме нашлось два или три серьезных косяка, которые подпортили общее впечатление. Но в целом вполне себе годно, можно смотреть.</p>
<p><a href="https://www.kinopoisk.ru/series/4652297/">Мертвый сезон (Hors Saison, первый сезон, 2022)</a>. Ну такое себе. В принципе, глянуть можно, но слишком часто придется восклицать "да что за фигня?!"</p>
<p><a href="https://www.kinopoisk.ru/series/4396499/">Маяк 23 (Beacon 23, первый сезон, 2023)</a>. Могло бы что-то получиться, если бы авторы в первом сезоне хоть сколько нибудь законченную историю рассказали. А то оборвали там, где ожидалась развязка. С явным приглашением подождать следующего сезона. Но это-то как раз и создает ощущение обманутых ожиданий и сильно разочаровывает.</p>
<p><b>Не смог досмотреть</b></p>
<p><a href="https://www.kinopoisk.ru/film/1006672/">Легенда о самбо (2022)</a>. Смог осилить всего минут двадцать. Показалось, что это редкостное говно не смотря на местами красочную и качественную картинку.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0tag:blogger.com,1999:blog-654279083390275842.post-2152731973880661152024-01-26T17:32:00.002+03:002024-01-26T17:32:42.660+03:00[prog.c++] Интересное чтиво про strict aliasing rule...<p>...лежит здесь: <a href="https://gist.github.com/shafik/848ae25ee209f698763cffee272a58f8">What is the Strict Aliasing Rule and Why do we care?</a></p>
<p>Документ далеко не новый, но если вы не в теме, то он будет, безусловно, полезен.</p>
<p>Из того, что стало откровением и открытием лично для меня (применительно к C++):</p>
<ul>
<li>в C++ содержимое другого объекта можно просматривать путем каста к char, unsigned char или std::byte. Т.е., грубо говоря, вы всегда можете сделать <tt>reinterpret_cast<char *>(other_pointer)</tt>. Но это не распространяется на signed char. И отдельная история с std::int8_t/uint8_t: эти типы могут быть простыми синонимами для char и unsigned char, а могут быть и отдельными, самостоятельными типами (в таком случае на них правило char/unsigned char/std::byte не распространяется);</li>
<li><p>оказывается, современные компиляторы могут понять, что делает std::memcpy и избавиться от реального вызова std::memcpy. Например, вот такой корректный способ получить float из int-а (при условии их одинаковых размеров):</p>
<code><pre>int src = ...;
float dst;
std::memcpy(&dst, &src, sizeof(src));</pre></code>
<p>в случае умного компилятора будет просто помещать в dst нужно значение без вызова memcpy;</p></li>
<li><p>не знал раньше про common initial sequence. А это, как выяснилось, может стать архиважной штукой при работе с union:</p>
<code><pre>struct A { char type; ... };
struct B { char type; ... };
struct C { char type; ... };
union U { A a; B b; C c; };
U u;
u.a = ...;
if(u.b.type == ...) // (1)
{...}</pre></code>
<p>Обращение к <tt>u.b</tt> в точке (1) легально не смотря на то, что U::b -- это неактивный в данный момент элемент union-а.</p>
</li>
</ul>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0tag:blogger.com,1999:blog-654279083390275842.post-58709272450390166192024-01-22T18:39:00.000+03:002024-01-22T18:39:43.817+03:00[prog.c++] Оказывается, в современном C++ параметры шаблона с дефолтными значениями можно располагать в начале списка параметров шаблона...<p>Был приятно удивлен тому, что вот это вполне себе компилируется и работает так, как мне и нужно:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#ff80ff">#include </font><font color="#ffa0a0"><iostream></font><br>
<br>
<font color="#60ff60">struct</font> no_size_limit {<br>
<font color="#60ff60">static</font> <font color="#60ff60">bool</font> is_size_valid(std::<font color="#60ff60">size_t</font> <font color="#80a0ff">/*</font><font color="#80a0ff">size</font><font color="#80a0ff">*/</font>) {<br>
std::cout << <font color="#ffa0a0">"no_size_limit::ensure_valid_size"</font> << std::endl;<br>
<font color="#ffff60">return</font> <font color="#ffa0a0">true</font>;<br>
}<br>
};<br>
<br>
<font color="#60ff60">template</font><<font color="#60ff60">typename</font> Size_Limiter=no_size_limit, <font color="#60ff60">typename</font>... Args><br>
<font color="#60ff60">void</font> f(Args && ...args) {<br>
<font color="#ffff60">if</font>(Size_Limiter::is_size_valid(<font color="#ffff60">sizeof</font>...(args))) {<br>
std::cout << <font color="#ffa0a0">"processing of args"</font> << std::endl;<br>
}<br>
<font color="#ffff60">else</font><br>
std::cout << <font color="#ffa0a0">"ignoring args"</font> << std::endl;<br>
}<br>
<br>
<font color="#60ff60">template</font><std::<font color="#60ff60">size_t</font> N><br>
<font color="#60ff60">struct</font> at_least {<br>
<font color="#60ff60">static</font> <font color="#60ff60">bool</font> is_size_valid(std::<font color="#60ff60">size_t</font> size) {<br>
std::cout << <font color="#ffa0a0">"at_least<"</font> << N << <font color="#ffa0a0">">::ensure_valid_size"</font> << std::endl;<br>
<font color="#ffff60">return</font> (N <= size);<br>
}<br>
};<br>
<br>
<font color="#60ff60">int</font> main() {<br>
f(<font color="#ffa0a0">1</font>, <font color="#ffa0a0">2</font>, <font color="#ffa0a0">3</font>, <font color="#ffa0a0">4</font>, <font color="#ffa0a0">5</font>);<br>
f<at_least<<font color="#ffa0a0">3</font>>>(<font color="#ffa0a0">1</font>, <font color="#ffa0a0">2</font>, <font color="#ffa0a0">3</font>, <font color="#ffa0a0">4</font>, <font color="#ffa0a0">5</font>);<br>
f<at_least<<font color="#ffa0a0">5</font>>>(<font color="#ffa0a0">1</font>, <font color="#ffa0a0">2</font>, <font color="#ffa0a0">3</font>);<br>
}<br>
</font></td>
</tr>
</tbody></table>
<p><a href="https://wandbox.org/permlink/TcfRVYesD5NAxPzC">Цынк</a></p>
<p>Так-то я со времен C++98 привык, что параметры шаблона со значениями по умолчанию идут в конце списка параметров шаблона. А тут потребовалось, чтобы они шли в начале. И оно раз и заработало.</p>
<p>Приятно.</p>
<hr/>
<p>На правах саморекламы: <a href="https://eao197.blogspot.com/2023/09/work-c.html">изобретаю велосипеды для себя, могу изобретать и для вас</a>.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com2tag:blogger.com,1999:blog-654279083390275842.post-85439662686502110822024-01-16T11:41:00.002+03:002024-01-16T14:19:03.866+03:00[linux] Если вам потребовался ArchLinux в Docker с пакетом из AUR...<p>...то вот эти ссылки могут оказаться полезны. По крайней мере мне помогли.</p>
<p><a href="https://wiki.archlinux.org/title/Arch_User_Repository">Arch_User_Repository</a>. Официальная информация о том, что такое AUR и как ставить пакеты из AUR. Имеет смысл просмотреть хотя бы по диагонали, чтобы понимать, что к чему и почему.</p>
<p><a href="https://dev.to/cloudx/testing-our-package-build-in-the-docker-world-34p0">Testing our package build in the Docker world</a>. В принципе, основная статья, в которой вроде бы все собрано воедино в нормальном, лаконичном и более менее понятном виде.</p>
<p><a href="https://blog.ganssle.io/articles/2019/12/gitlab-ci-arch-pkg.html">Testing an Arch Linux package in Gitlab CI</a>. Статья не совсем про Docker, но мне она оказалась наиболее полезна, т.к. там расписывается что и зачем делается.</p>
<p>То, что заработало именно для меня можно найти <a href="https://github.com/Stiffstream/restinio-dockerfiles/blob/master/v0.7/archlinux-gcc-system-sobjectizer.Dockerfile#L14-L27">здесь</a>. Но прошу гнилыми помидорами не бросаться, я не настоящий <strike>сварщик</strike> линуксоид, и даже не продвинутый пользователь ;)</p>
<p>ЗЫ. Т.к. после экспериментов с Docker-ом остается куча всяких устаревших (и не очень) образов, то найти простые способы поудалять лишние Docker-овские images и containers можно здесь: <a href="https://www.digitalocean.com/community/tutorials/how-to-remove-docker-images-containers-and-volumes">How To Remove Docker Images, Containers, and Volumes</a>. Например:</p>
<code><pre>$ docker image prune
$ docker rmi $(docker images -a -q)
$ docker rm $(docker ps -a -f status=exited -q)
$ docker stop $(docker ps -a -q)
$ docker rm $(docker ps -a -q)
</pre></code>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com2tag:blogger.com,1999:blog-654279083390275842.post-69642240922952572952024-01-10T18:13:00.000+03:002024-01-10T18:13:04.939+03:00[prog.c++.wtf] Публичный член приватного вложенного типа?<p>Еще одно открытие для меня в языке C++, которым пользуюсь уже больше 30 лет:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#90f020">class</font> Outer {<br>
<font color="#90f020">struct</font> Inner {<br>
<font color="#90f020">int</font> m_a{};<br>
<font color="#90f020">int</font> m_b{};<br>
};<br>
<font color="#ffff60">public</font>:<br>
Inner m_i;<br>
};<br>
<br>
<font color="#90f020">int</font> main()<br>
{<br>
Outer o;<br>
o.m_i.m_a = <font color="#ffa0a0">3</font>;<br>
o.m_i.m_b = <font color="#ffa0a0">4</font>;<br>
}<br>
</font></td>
</tr>
</tbody></table>
<p>Оказывается, так можно. <a href="https://wandbox.org/permlink/m7bFg84g4XyMbk2l">Цынк</a>.</p>
<p>Что меня выморозило в этом примере: мы же класс Inner сделали закрытым вложенным классом для Outer. Т.е. вроде как, по логике вещей, классом Inner могут пользоваться только сам Outer и его друзья.</p>
<p>Но ничего нам не запрещает объявить в Outer публичный член <tt>Outer::m_i</tt> приватного, вроде бы, типа Outer::Inner. И любой желающий может работать с таким объектом приватного типа Outer::Inner.</p>
<p>Впервые с таким столкнулся. Я, честно говоря, ожидал, что компилятор не должен позволить объявить публичный <tt>Outer::m_i</tt>. Но, в очередной раз, ошибся 🥴</p>
<hr/>
<p>На правах саморекламы: <a href="https://eao197.blogspot.com/2023/09/work-c.html">изобретаю велосипеды для себя, могу изобретать и для вас</a>.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0tag:blogger.com,1999:blog-654279083390275842.post-41436295499774468112024-01-09T13:31:00.000+03:002024-01-09T13:31:04.309+03:00[prog.c++] Оказывается, в современном C++ нельзя взять и сложить std::string с std::string_view...<p>На пятый год работы с C++17, в котором std::string_view появился, "Зоркий глаз" (т.е. я) заметил, что в C++ пока нет версии <tt>operator+</tt> для случая std::string и std::string_view :(</p>
<p>Поэтому ни в C++17, ни в C++20, ни, подозреваю, в C++23, не получится написать так:</p>
<code><pre>std::string f(std::string_view a, std::string_view b) {
using namespace std::string_view_literals;
return std::string{"Expected value: "} + a + ", actual value: "sv + b;
}</pre></code>
<p>Но есть <a href="https://wg21.link/p2591">пропозал</a>. И, может быть, нам повезет и в C++26 эта фича в языке таки появится. А может только в C++29...</p>
<p>Если честно, то я, мягко говоря, в шоке.</p>
<hr/>
<p>На правах саморекламы: <a href="https://eao197.blogspot.com/2023/09/work-c.html">изобретаю велосипеды для себя, могу изобретать и для вас</a>.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0tag:blogger.com,1999:blog-654279083390275842.post-1939280895764525342023-12-31T09:39:00.001+03:002023-12-31T09:39:18.208+03:00[life.cinema] Очередной кинообзор (2023/12)<p>Подошло время финального обзора в 2023-ем году. Традиционно, в начале каждого из списков идет то, что понравилось больше. Хотя, в данном случае, практически ничего и не понравилось, все перечисленное просто разной степени паршивости :(</p>
<p><b>Фильмы</b></p>
<p><a href="https://www.kinopoisk.ru/film/5135249/">Призраки в Венеции (A Haunting in Venice, 2023)</a>. Атмосферно. Но, пожалуй, это единственное достоинство данного фильма.</p>
<p><a href="https://www.kinopoisk.ru/film/4816708/">Чарли-Пуля (Fast Charlie, 2023)</a>. Очень скромно и бюджетно. Но если больше смотреть нечего, то сойдет.</p>
<p><a href="https://www.kinopoisk.ru/film/4908570/">Немая ярость (Silent Night, 2023)</a>. Во многом чувствуется рука старого мастера. Во второй части даже и бодрый экшен имеется. Но не цепляет особо. Для меня это тот самый случай когда компьютерная графика убивает жанр боевика, т.к. когда видишь что и выстрелы и разбитые машины нарисованы на компьютере, то всерьез воспринимать происходящее на экране уже сложно.</p>
<p><a href="https://www.kinopoisk.ru/film/1430638/">Большая игра (The Independent, 2022)</a>. Посредственное кино на специфическую американскую тему и, как мне показалось, исключительно на внутреннюю аудиторию. Да еще и с BLM-повесточкой из-за которой положительные персонажи -- черные, а все отрицательные -- белые. Удивлен почему главную героиню не сделали одноногой лесбиянкой, ну чтобы уж совсем в трендах быть.</p>
<p><a href="https://www.kinopoisk.ru/film/4947546/">Девичник: Убойная ночь (Fear the Night, 2023)</a>. Убогая попытка снять кино про то, как сильная и отважная женщина дает отпор грубым и тупым мужланам. Посмотреть можно разве что если больше смотреть ну вот совсем нечего. Вот совсем-совсем нечего :)</p>
<p><b>Сериалы</b></p>
<p><a href="https://www.kinopoisk.ru/series/900004/">Корпорация (Incorporated, первый сезон, 2016)</a>. Средненько, но смотрибельно, потраченного времени не жалко. Больше всего портит впечатление то, что законченной истории создатели не рассказали, а оставили много заделов на следующий сезон... Которого у сериала не оказалось :(</p>
<p><a href="https://www.kinopoisk.ru/series/5061176/">Темное дитя: Отголоски (Orphan Black: Echoes, первый сезон, 2023)</a>. В принципе, интересная история и интересные вопросы подняты. Но! Во-первых, как обычно для сериалов, слишком затянуто. Как по мне, так можно было раза в два хронометраж сократить. Во-вторых, в конце первого сезона они не стали даже жирную запятую ставить в своей истории. Тупо оборвали происходящее и типа "ждите следующего сезона". За что от меня жирный минус.</p>
<p><b>Сериал без рейтинга</b></p>
<p><a href="https://www.kinopoisk.ru/series/5304403/">Слово пацана. Кровь на асфальте (первый сезон, 2023)</a>. Снято круто, картинка и передача атмосферы и деталей быта тех лет просто отличные. Но смотреть было не интересно, ничего из происходящего не трогало, а какие-то моменты вызывали вопросы "ну как так-то?" Пришлось досмотреть просто чтобы не оказаться в ситуации "Пастернака не читал, но осуждаю". Почему вокруг этого сериала такой хайп решительно непонятно.</p>
<p><b>Не удалось досмотреть</b></p>
<p><a href="https://www.kinopoisk.ru/film/4958668/">Триггер. Фильм</a>. Первые минут 30-40 оставили впечатление, что это какая-то редкостная дрянь. Дальше осилить не смог.</p>
<p><b>Что из новинок 2023-го года я мог бы вспомнить с удовольствием?</b></p>
<p>Просто в качестве некого краткого подведения киноитогов 2023-го. Пожалуй, выделю два фильма и один сериал. И все три российского производства.</p>
<p>Во-первых, это очередная часть "<a href="https://www.kinopoisk.ru/film/5045951/">О чем говорят мужчины</a>". Ну вот заходит в меня то, что "Квартет И" рассказывает в этой своей серии, что уж тут поделать :)</p>
<p>Во-вторых, это "<a href="https://www.kinopoisk.ru/film/4448519/">Вызов</a>". Был сильно и приятно удивлен уровню, на котором все было сделано. Впечатляет.</p>
<p>В-третьих, это "<a href="https://www.kinopoisk.ru/series/4639604/">Без правил</a>". Просто потому, что все настолько динамично, что вообще нет ощущения, что смотришь сериал.</p>
<p>В-четвертых, это "<a href="https://www.kinopoisk.ru/film/4686066/">Поехавшая</a>". Просто хорошее, доброе и жизнеутверждающее кино, хотя клиповый монтаж подпортил впечатление.</p>
<p>И да, у меня, как у программиста, типичные проблемы с плюс-минус единичкой ;)</p>eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0tag:blogger.com,1999:blog-654279083390275842.post-4186523872026100092023-12-26T19:18:00.000+03:002023-12-26T19:18:19.266+03:00[prog.c++] Сочетание ключиков -source-charset:windows-1251 и -execution-charset:utf-8 в Visual C++<p>Не смотря на то, что в мире Linux-ов уже давным давно все на UTF-8, под Windows еще встречаются исходники с комментариями и не-юникодными строковыми/символьными литералами в CP1251. При этом Microsoft добавила в свой компилятор ключик -execution-charset, который указывает, в каком представлении строковые константы будут представлены в результирующем exe/dll файле. Соответственно, значение utf-8 для -execution-charset указывает, что в run-time ваши narrow string-и будут на самом деле в Unicode, в UTF-8 представлении. Как в Linux-е.</p>
<p>Но, если у вас narrow string литералы в исходнике в CP1251, то компилятору нужно указать это, чтобы он правильно сделал их конверсию в UTF-8. Для чего предназначен ключик -source-charset и значение windows-1251 для него.</p>
<p>Однако, указав VC++ компилятору и -source-charset:windows-1251, и -execution-charset:utf-8, можно сделать для себя удивительные открытия.</p>
<p>Вот, например, простая программа:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#ff80ff">#include </font><font color="#ffa0a0"><iostream></font><br>
<font color="#ff80ff">#include </font><font color="#ffa0a0"><typeinfo></font><br>
<br>
<font color="#90f020">int</font> main()<br>
{<br>
<font color="#90f020">const</font> <font color="#90f020">char</font> str[]{ <font color="#ffa0a0">"Привет!"</font> };<br>
std::cout << <font color="#ffff60">sizeof</font>(str) << std::endl;<br>
}<br>
</font></td>
</tr>
</tbody></table>
<p>Когда мы ее компилируем только с -source-charset:windows-1251, то результат 8. Что вполне ожидаемо, т.к. в слове "Привет" шесть букв, плюс восклицательный знак, плюс финальный ноль-символ.</p>
<p>Но если мы добавим ключик -execution-charset:utf-8, то результатом будет уже 14.</p>
<p>Почему? Потому что каждый русский символ будет представлен уже двумя байтами в UTF-8, плюс восклицательный знак, плюс ноль-символ.</p>
<p>Но еще интереснее, если мы попытаемся задать массив char-ов посимвольно. Что-то вроде:</p>
<code><pre>const char s[]{ 'П', 'р', 'и', 'в', 'е', 'т'};</pre></code>
<p>Тут мы получим предупреждение компилятора об усечении значения при приведении его к типу char.</p>
<p>Но откуда это самое усечение возьмется?</p>
<p>А давайте посмотрим на это с помощью другой простенькой программы:</p>
<table width="100%" bgcolor="#000040"><tbody>
<tr>
<td><font color="#c0c0c0">
<font color="#ff80ff">#include </font><font color="#ffa0a0"><iostream></font><br>
<font color="#ff80ff">#include </font><font color="#ffa0a0"><typeinfo></font><br>
<br>
<font color="#90f020">template</font><<font color="#90f020">typename</font> T><br>
<font color="#90f020">char</font> to_char(T v) <font color="#ffff60">noexcept</font><br>
{<br>
<font color="#ffff60">using</font> TU = std::make_unsigned_t<T>;<br>
<br>
std::cout << <font color="#ffa0a0">"type is: "</font> << <font color="#ffff60">typeid</font>(T).name() << <font color="#ffa0a0">", value: 0x"</font><br>
<< std::hex << <font color="#ffff60">static_cast</font><<font color="#90f020">unsigned</font>>(<font color="#ffff60">static_cast</font><TU>(v)) << std::dec << std::endl;<br>
<font color="#ffff60">return</font> <font color="#ffff60">static_cast</font><<font color="#90f020">char</font>>(v);<br>
}<br>
<br>
<font color="#90f020">void</font> dump_hex(<font color="#90f020">const</font> std::string & w)<br>
{<br>
std::cout << std::hex;<br>
<font color="#ffff60">for</font>(<font color="#90f020">const</font> <font color="#90f020">auto</font> ch : w)<br>
std::cout << <font color="#ffa0a0">"0x"</font> << <font color="#ffff60">static_cast</font><<font color="#90f020">unsigned</font>>(<font color="#ffff60">static_cast</font><<font color="#90f020">unsigned</font> <font color="#90f020">char</font>>(ch)) << <font color="#ffa0a0">", "</font>;<br>
std::cout << std::dec << std::endl;<br>
}<br>
<br>
<font color="#90f020">int</font> main()<br>
{<br>
to_char( <font color="#ffa0a0">'П'</font> );<br>
dump_hex( <font color="#ffa0a0">"П"</font> );<br>
}<br>
</font></td>
</tr>
</tbody></table>
<p>Если мы скомпилируем ее только с ключиком -source-charset:windows-1251, то получим:</p>
<code><pre>type is: char, value: 0xcf
0xcf,</pre></code>
<p>Т.е. вполне ожидаемо символьный литерал у нас имеет тип char.</p>
<p>А если добавим -execution-charset:utf-8, то:</p>
<code><pre>type is: int, value: 0xd09f
0xd0, 0x9f,</pre></code>
<p>Оказывается, что у символьного литерала уже тип int.</p>
<p>Что, в принципе, понятно. Но оказалось неожиданно :)</p>
<hr/>
<p>На правах саморекламы: <a href="https://eao197.blogspot.com/2023/09/work-c.html">изобретаю велосипеды для себя, могу изобретать и для вас</a>.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0tag:blogger.com,1999:blog-654279083390275842.post-79032945180443632072023-12-23T16:37:00.001+03:002023-12-23T16:37:57.488+03:00[life.audiophilia.diy] Итоги уходящего года в сфере самодельных наушников и мои текущие предпочтения<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigBvGOWpOkSS_y1ko1Ch6bWaCMXkspj1F8UclnbXtZ75pidaTJJ8juAsCgIn4d7Z1-B6w3U8NJ2rKwJw0vIXbhQHMP4F5deL5loUrYEcpaJOAnyVilS3A2AJI4Vk8zMCjIjssfGg7rU1flbPtQP366SBy71XFxZitYQBHph2smRmS8AsjzIpyrOW5TJm4/s3612/2023_diy_earbuds.jpg" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="600" data-original-height="2617" data-original-width="3612" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigBvGOWpOkSS_y1ko1Ch6bWaCMXkspj1F8UclnbXtZ75pidaTJJ8juAsCgIn4d7Z1-B6w3U8NJ2rKwJw0vIXbhQHMP4F5deL5loUrYEcpaJOAnyVilS3A2AJI4Vk8zMCjIjssfGg7rU1flbPtQP366SBy71XFxZitYQBHph2smRmS8AsjzIpyrOW5TJm4/s600/2023_diy_earbuds.jpg"/></a></div>
<p>Сразу приношу свои извинения за то, что приведенный ниже персональный ТОП динамиков для DIY-вкладышей публикую уже после того, как на Aliexpress закончились все ноябрьские и декабрьские скидки :(</p>
<p>Но как раз эти самые скидочные периоды заставили меня всерьез оценить имеющуюся коллекцию, чтобы расставить свои текущие приоритеты и предпочтения по местам. На что потребовалось гораздо больше времени, сравнений и раздумий, чем предполагал. Поэтому получилось как получилось, дико извиняюсь. Кто хочет немного сэкономить может подождать следующих распродаж.</p>
<p>Итак, уходящий 2023-й год для моего увлечения самодельными наушниками (даже более точно: самодельными наушниками-вкладышами) прошел, в целом, неудачно. Было приобретено несколько комплектов дорогих (для меня) динамиков, но мало что понравилось. Да еще и два самых дорогих комплекта были мной сломаны в процессе подбора корпусов. Такое бывает: приобретаешь динамики за $40, а потом раз! и они ломаются при установке в корпус или при извлечении из корпуса, в котором их звучание не понравилось. Это DIY, тут никто тебе никаких гарантий не дает, все от самого себя зависит.</p>
<p>Да еще и с лета 2023 на Aliexpress с покупателей из РБ стали взимать 20%НДС, так что это мое увлечение внезапно стало заметно дороже :(</p>
<p>Пожалуй, единственным светлым пятном было знакомство с (относительно) недорогими 15.4mm динамиками с композитной титановой диафрагмой, о которых речь еще пойдет ниже. Отличный звук за свои деньги, даже не ожидал.</p>
<p>Вот такие вот итоги. Теперь же можно перейти к приятной части, к перечислению того, что мне нравится. Кому интересно, милости прошу под кат.</p>
<a name='more'></a>
<p>Сразу отмечу, что ссылки буду давать либо на те магазины и товары, которые я сам покупал, либо на те магазины, в которых динамики можно купить сейчас. Со временем ссылки могут протухнуть, я это дело не отслеживаю, так что если через полгода-год что-то открываться нормально не будет, то прошу понять и простить.</p>
<hr/>
<p>Сперва две пары динамиков, которые, как по мне, просто лидеры в своей категории. Но, так уж оказалось, что это не мои категории. Я их периодически слушаю. Иногда больше, иногда меньше. Но пока что распробовать для длительного прослушивания не смог. Так что не смотря на отличный в техническом плане звук, это пока что не мой звук.</p>
<ul>
<li><a href="https://aliexpress.ru/item/1005005285935360.html">14.2mm карбоновые, 32ом</a>. Самые крутые НЧ из того, что мне доводилось слышать. Такая глубина и проработка, что просто на первое время забываешь про все остальные частоты. Но мне в них не хватает ВЧ, сильно не хватает. Сюда бы ВЧ от LCP/DLC и был бы настоящий end-game.</li>
<li><a href="https://aliexpress.ru/item/1005005883980850.html">15.4mm композит из литиево-магниевого сплава, 32ом</a>. Возможно, самые техничные и нейтральные из побывавших у меня. С приятной тоналкой. С неожиданно неглубокой и не широкой сценой для вкладышей. Но при этом с поразительной передачей трехмерности и разделением между инструментами. Звук в них ощущается не так, как в других описанных здесь динамиках: более камерно, что ли. К сожалению, для меня они пока слишком светлые, не хватает мне в них веса на НЧ.</li>
</ul>
<p>Могу сказать, что в плане техничности эти две пары динамиков свою цену полностью оправдывают. Но вот насколько их звучание понравится -- это уже субъективное дело.</p>
<hr/>
<p>Теперь же мой ТОП-4. Изначально предполагалось, что будет всего лишь ТОП-2, но динамики с CNT и с композитной титановой диафрагмой не так уж и сильно отстают от LCP/DLC, так что для меня они все же ТОПовые.</p>
<p>Описания расположены в порядке убывания предпочтений. Т.е. LCP-динамики мне нравятся больше, чем с композитной титановой диафрагмой. Но преимущество здесь, по совокупности, в каких-то 10-15%, ни о каком драматическом разрыве нет и речи.</p>
<ul>
<li><p><a href="https://aliexpress.ru/item/1005005847956910.html">15.4mm LCP-диафрагма, 32ом</a>. Ставлю ссылку на магазин, где их можно выгодно купить сейчас. Но свои я покупал еще в 2021-ом, когда LCP-динамики только-только появились, и в магазине, которого уже нет. Так что остается надеятся, что продающиеся сейчас LCP-динамики не хуже моих, хотя их уже и отдают в два раза дешевле, чем в 2021-ом.</p>
<p>По звуку отличные НЧ и, наверное, самые лучшие ВЧ из услышанного мной. Возможно, только у DLC-динамиков ВЧ могут соперничать. На мой вкус, серединка чуть провалена, из-за чего вокал иногда звучит как бы чуть позади других инструментов.</p>
<p>Еще одна претензия -- это то, что НЧ ощущаются не такими хлесткими, ударными и упругими как в описанных ниже CNT-драйверах. Но все равно НЧ отличные. Что сейчас даже кажется забавным, т.к. два года назад мне LCP-динамики казались излишне светлыми и вовсе не басовитыми. Сейчас же предпочтения изменились и НЧ в LCP чуть ли не идеал.</p>
<p>В общем, для меня LCP являются наиболее универсальными из всех здесь описанных.</p></li>
<li><a href="https://aliexpress.ru/item/1005004043877192.html">15.4mm DLC-диафрагма, 32ом</a>. В отличии от LCP здесь чуть больше акцент на серединку и чуть меньше НЧ. Поэтому звучат они немного светлее и ровнее, чем LCP. Хоть НЧ и поменьше, но они немного более хлесткие и упругие. В принципе, DLC-динамики могли бы стать моими фаворитами вместо LCP, но в некоторых жанрах при длительном прослушивании начинает не хватать массы на НЧ, здесь различия с LCP как раз начинают сказываться. Отличным примером является замечательный альбом <a href="https://music.yandex.ru/album/1610460">Head Movies</a> от nobody.one: когда слушаешь его полностью, то ближе к концу начинает казаться, что DLC слишком светлые, хочется чего-то "потемнее".</li>
<li>15.4mm CNT-диафрагма, 32ом. Свои брал <a href="https://aliexpress.ru/item/1005004267087946.html">здесь</a>, но такие же чуть подешевле можно найти <a href="https://aliexpress.ru/item/1005005889355699.html">здесь</a>. Очень приятная теплая тоналка, просто шикарные НЧ и, на удивление, неплохие для темных наушников ВЧ. Хотя по ВЧ они явно проигрывают LCP и DLC-динамикам. Для некоторых жанров самое то. Но, как по мне, здесь акцент смещен в сторону НЧ и нижней середины, что при длительном прослушивании таки утомляет, хочется чуть больше воздуха и пространства. Плюс к тому, из всех перечисленных здесь динамиков у них наиболее близко расположенная к слушателю сцена. Иногда это ОК, но далеко не всегда.</li>
<li><a href="https://aliexpress.ru/item/1005005896474475.html">15.4mm композитная титановая диафрагма, 32ом</a>. Самая приятная тоналка из всего здесь упомянутого. Для своей цены все хорошо и с НЧ, и с СЧ, и с ВЧ. Хотя на ВЧ, конечно же, деталей и протяженности не хватает, если сравнивать с LCP/DLC. Для каких-то жанров, вроде испанской гитары и легкого инструментального джаза, вот просто самое оно. Мне кажется, что звук струн классической гитары эти динамики передают просто лучше всех. Однако, когда инструментов в композиции много (например, целый оркестр), то звучание оказывается слишком уж "монолитным", не знаю как лучше выразить. Отличным примером служит альбом <a href="https://www.discogs.com/ru/master/739078-John-Williams-Plays-The-Movies">Plays The Movies</a> от гитариста Джона Уилльямса. Когда вместе с гитарой вступает оркестр, то сразу чувствуется, как эти динамики уступают вышеупомянутым :( Так что, увы, не самые универсальные.</li>
</ul>
<p>Если попытаться дать какие-то рекомендации, то если хочется выслушивать мельчайшие детали и переливы на ВЧ, то LCP/DLC. Если хочется теплого звука и мощного баса, то CNT или титановый композит.</p>
<hr/>
<p>Напоследок пару слов для тех, кто хочет просто нормальные вкладыши за копейки, но которые бы отлично звучали от телефона или планшета/ноутбука. По идее, если вы обычный меломан, не обладающий продвинутой аппаратурой и слушающий с телефона стримминги или свою старую коллекцию MP3-файлов (или даже FLAC-ов, но не хайрезных), то на любых из описанных ниже динамиков можно остановиться на ближайшие лет 5-6. Часто я сам жалею, что не сделал этого в 2021-ом :)</p>
<ul>
<li><a href="https://aliexpress.ru/item/1005004024155707.html">15.4mm 32ом (с голубым лаком)</a>. Легкие на раскачку, отлично звучат от телефона. Хорошие НЧ, СЧ и ВЧ, приятная тоналка. Но хоть ВЧ и громкие, но недостаточно детальные. Иногда в этих динамиках слышишь на ВЧ просто какое-то мутноватое шипение там, где в более дорогих динамиках слышны более четкие отзвуки и призвуки. Но, блин, за свои деньги просто огонь. Всякие Sennheiser MX170 или Kbear Stellar и рядом не валялись.</li>
<li><a href="https://aliexpress.ru/item/1005004043825734.html">15.4mm 32ом (с красным лаком и красным ободком)</a>. В отличии от предыдущих требуют более серьезного усиления, от телефона не раскроются, тут нужен ЦАП под 100mW на 32ом. Но зато ВЧ у них намного более чистые и детальные. Если сравнивать с более дорогими наушниками из данного обзора, то главный недостаток этих динамиков -- более резкий и простой звук. Скажем, саксофон на LCP/DLC-драйверах звучит более интересно, чем на этих. Но это проявляется разве что когда сравниваешь лоб-в-лоб, либо когда знаешь что выслушивать.</li>
</ul>
<p>Для хардкорных басхедов, которым нужно ну очень много баса, могу посоветовать <a href="https://aliexpress.ru/item/1005005026991534.html">вот эти 15.4mm драйвера с композитной бериллиевой диафрагмой</a>. Больше баса нужно еще поискать (и вряд ли найти). Но, что удивительно, у них совершенно шикарные за для своей цены ВЧ, которые запросто могут спорить с ВЧ от LCP/DLC. Да, у LCP/DLC будут таки получше, но блин, разница-то в стоимости какая. Однако, если вы не басхед, то от такого притемненного звука и откровенно проваленной серединки, быстро устанете. Что интересно: как мне кажется, именно эти динамики стоят в известных в узких кругах наушника "кошачьи лапки" модель "Спецэффект". Только вот "Спецэффект" продают на Avito за 5000+ RUR, тогда как динамики на Aliexpress с доставкой обойдутся в $5.</p>
<p>Если 15.4mm динамики для ваших ушей слишком крупные, то мне и, особенно, моей дочке очень зашли <a href="https://aliexpress.ru/item/1005004071524376.html">вот эти 14.8mm динамики с титановой диафрагмой</a>.</p>
<hr/>
<p>Пару слов о дальнеших планах.</p>
<p>Пора с этим увлечением завязывать ;)</p>
<p>Поскольку главный вопрос у меня сейчас, когда я вижу какую-то многообещающую новинку на Aliexpress: "А куда девать те, что у меня есть, и которые мне очень нравятся?"</p>
<p>Ответа на этот вопрос нет. Наушников уже сейчас больше, чем мне нужно. Продавать на барахолке не интересно, на DIY-вкладыши нет спроса. А просто складывать в коробку наушники с отличным звуком жалко: они должны звучать, а не пылиться на полке.</p>
<p>Так что, возможно, раз-два в год буду брать что-то из новинок. Просто чтобы узнать куда продвинулось динамикостроение. А так уже есть ощущение, что наушниками себя обеспечил лет на 10 вперед, слушать не переслушать :)</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0tag:blogger.com,1999:blog-654279083390275842.post-36745454677861759752023-12-22T08:41:00.001+03:002023-12-22T08:41:37.681+03:00[blog] Happy birthday my blog - 15!<p>Как-то тихо подкрался очередной ДР блога. Ровно 15 лет назад здесь была опубликована первая заметка.</p>
<p>Пожалуй, не буду делать настолько же пространный отчет, как <a href="https://eao197.blogspot.com/2018/12/blog-happy-birthday-my-blog-10.html">пять лет назад</a>. Да и рассказывать особо нечего, последние годы прошли в непростой борьбе за выживание нашей маленькой софтверостроительной компании. Из-за чего все остальное было задвинуто на задний план, а жизнь, моментами, как будто поставлена на паузу. Похвастаться тут особо нечем, кроме того, что мы все еще живы и все еще помогаем тем, кто в наших услугах нуждается.</p>
<p>Оглядываясь назад могу сказать, что когда блог только начинался, даже и мысли не было, что он просуществует так долго. А теперь уже начинает казаться, что и рубеж в 20 лет вполне можно будет взять. Если, конечно, и я, и blogger.com, проживем еще пять лет :)</p>
<p>Сейчас уже сложно вспомнить почему вообще блогингом занялся. Типа модно тогда было -- "все побежали и я побежал" -- как-то так. Хотя было у меня опасение, что мода на блоги уже уходит. Ну да все равно начал.</p>
<p>Теперь же это уже часть образа жизни. Или часть самой жизни, тут уже не знаю как точнее выразиться.</p>
<p>Что удивительно, так это то, что блог все еще кто-то читает. А иногда еще и комментирует. За что, конечно же, большое спасибо подписчикам и посетителям. Не переключайтесь. Уж не знаю, сколько здесь еще будет чего и будет ли среди этого что-то интересное. Но что-то будет. Силы и желание пока есть. Поэтому движемся дальше.</p>eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com3tag:blogger.com,1999:blog-654279083390275842.post-77071191136027256212023-12-21T16:42:00.000+03:002023-12-21T16:42:31.980+03:00[prog.bugs] Иногда цена ошибки может быть известна достаточно точно<p>Некоторое время назад один разработчик вызвал некую функцию и забыл проконтролировать ее код возврата. В большинстве случаев эта функция завершалась нормально и все работало как полагается.</p>
<p>Но не всегда. Иногда функция возвращала ненулевой код ошибки, но этот код никто не проверял. Программа продолжала свои действия как ни в чем не бывало, но местами работала уже неправильно.</p>
<p>Когда такое поведение обнаружилось, другой разработчик начал разбираться с причинами проблемы. И добрался до злополучной функции...</p>
<p>Так уж получилось, что этот другой разработчик залогировал часы работы, потраченные на поиск и исправление проблемы. Тем самым оказалось легко вычислить стоимость ошибки путем умножения количества затраченных часов на стоимость одного часа работы.</p>
<p>ЗЫ. Все совпадения с реальными людьми и событиями случайны и непреднамеренны :)</p>
<hr/>
<p>Мораль сей басни: если вызывается какая-то функция, которая возвращает код ошибки, то код ошибки обязательно должен быть проверен. Обязательно. Должен. Быть. Проверен.</p>
<p>Далее, в случае возникновения ошибки, по ситуации:</p>
<ul>
<li>если ошибка ожидаемая, то выполняется логика ее обработки. Например, попробовали открыть файл, не получилось, что-то сделали по этому поводу. Скажем, выдали сообщение пользователю и попросили ввести новое имя файла;</li>
<li>если ошибка не сильно ожидаемая и не предполагающая путей исправления здесь и сейчас, то либо возвращаем свой код ошибки наверх (если исключения под запретом), либо выбрасываем исключения. Например, вызвали malloc, а он взял и вернул NULL. Маловероятно, но потенциально может произойти. Проверили результат malloc-а и вернули код ошибки наверх;</li>
<li>если ошибка вообще из разряда невероятных, то либо действуем как в предыдущем пункте, либо же вообще тупо зовем abort. Например, вызываем <a href="https://linux.die.net/man/3/getwd">getwd</a>, а получаем NULL, хотя казалось бы как такое возможно?</li>
</ul>
<p>Выброс исключения или вызов abort в невероятных (на первый взгляд) ситуациях -- это надежная гарантия того, что когда что-то пойдет не так (а оно пойдет, в этом можно не сомневаться), то вы:</p>
<ul>
<li>во-первых, сразу же узнаете о проблеме и</li>
<li>во-вторых, не сможете тихо "замести ее под коврик".</li>
</ul>
<p>Такая паранойя поможет вам сэкономить не одну тысячу рублей. И, иногда, вы даже сможете подсчитать сколько именно могли бы сэкономить если бы сразу следовали бы этим простым правилам.</p>
<hr/>
<p>На правах саморекламы: <a href="https://eao197.blogspot.com/2023/09/work-c.html">изобретаю велосипеды для себя, могу изобретать и для вас</a>.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0tag:blogger.com,1999:blog-654279083390275842.post-87937967482050986752023-12-14T18:01:00.001+03:002023-12-14T18:02:00.598+03:00[prog.c++] Несколько не очень приятных открытий...<p>...ну или я просто пока не понял, как это можно "правильно приготовить" :(</p>
<hr/>
<p>Впервые довелось столкнуться с Google Test (далее gTest). Ранее пользовался либо чем-то самодельным, либо Catch2, либо Doctest. А вот теперь и до Google Test очередь дошла.</p>
<p>В принципе, мощная штука, но одна вещь оттуда сейчас чуть ли не show-stopper-ом выступила.</p>
<p>Дело в том, что там можно определить свой метод SetUpTestSuite для подготовки test-suite к исполнению. И мне в этом методе нужно загрузить некие данные, которые затем потребуются в test-cases, входящих в мой test-suite.</p>
<p>Но засада оказалась в том, что если в SetUpTestSuite возникает исключение (например, по какой-то причине я не смог загрузить нужные мне данные), то gTest все равно пытается выполнить входящие в test-suite test-cases. Хотя без нужных данных все эти test-cases завершаются негативно. А вот гарантировать отсутствие исключений я не могу -- иногда данные могут и не загружаться.</p>
<p>И я пока не понял, как поступать в этих случаях. Неужели просто дергать std::abort внутри SetUpTestSuite?</p>
<hr/>
<p>После долгих-долгих лет работы вне IDE потребовалось таки воспользоваться VisualStudio. Надеюсь, что это временно, однако пока нужно. Плеваться приходится много, но это тема отдельного разговора.</p>
<p>Вот что стало по-настоящему неприятным сюрпризом, так это то, что когда инициируешь запуск тестов из Test Explorer, то студия напрочь выбрасывает все, что печатается в stderr/stdout. Грубо говоря, есть у тебя в тесте какая-то тестовая печать в std::cout... И все, забудь! Что там было напечатано из Test Explorer не видно :(</p>
<p>Очень надеюсь, что это я неграмотный идиот и просто не знаю куда смотреть. Может там есть какая-то волшебная кнопка, чтобы показать stderr/stdout от тестов?</p>
<hr/>
<p>В C++20 добавили char8_t. Удобная штука, чтобы конструировать, например, std::filename::path не беспокоясь о том, как это будет воспринято. Например, под Windows такая запись:</p>
<code><pre>std::filename::path my_path{u8"./Файл-С-Именем-На-Русском-Языке.你好.txt"};</pre></code>
<p>теперь обрабатывается уже полностью корректно. А вот в рамках C++17, насколько я помню, будет облом в run-time.</p>
<p>Еще в C++20 добавили u8string_view. Это как string_view, но для char8_t.</p>
<p>Но, оказалось, что u8string_view в рамках C++20 нельзя передать в std::format. <a href="https://wandbox.org/permlink/QvgWo73ouDlhjqMe">Цынк</a>.</p>
<p>Ну и да, еще и диагностика как всегда в C++ ;)</p>
<p>Кстати говоря, как я понял, нельзя заставить <a href="https://en.cppreference.com/w/cpp/utility/format/format">std::format</a> делать std::u8string. Можно либо std::string, либо std::wstring.</p>
<hr/>
<p>На правах саморекламы: <a href="https://eao197.blogspot.com/2023/09/work-c.html">изобретаю велосипеды для себя, могу изобретать и для вас</a>.</p>
eao197http://www.blogger.com/profile/17283739752119445290noreply@blogger.com0