C++ Embedded
Ir al canal en Telegram
Леденящие душу прохладные истории про С++ в embedded проектах. Зарисовки из разработки встраиваемых систем.
Mostrar más446
Suscriptores
+124 horas
+27 días
+430 días
Carga de datos en curso...
Canales Similares
Sin datos
¿Algún problema? Por favor, actualice la página o contacte a nuestro gerente de soporte.
Nube de Etiquetas
Sin datos
¿Algún problema? Por favor, actualice la página o contacte a nuestro gerente de soporte.
Menciones Entrantes y Salientes
---
---
---
---
---
---
Atraer Suscriptores
agosto '26
agosto '26
+1
en 0 canales
julio '26
+7
en 0 canales
Get PRO
junio '26
+5
en 0 canales
Get PRO
mayo '26
+6
en 0 canales
Get PRO
abril '26
+9
en 0 canales
Get PRO
marzo '26
+2
en 0 canales
Get PRO
febrero '26
+9
en 0 canales
Get PRO
enero '26
+3
en 0 canales
Get PRO
diciembre '25
+9
en 0 canales
Get PRO
noviembre '25
+14
en 0 canales
Get PRO
octubre '25
+3
en 0 canales
Get PRO
septiembre '25
+2
en 0 canales
Get PRO
agosto '25
+12
en 0 canales
Get PRO
julio '25
+13
en 0 canales
Get PRO
junio '25
+9
en 0 canales
Get PRO
mayo '25
+12
en 0 canales
Get PRO
abril '25
+7
en 0 canales
Get PRO
marzo '25
+21
en 0 canales
Get PRO
febrero '25
+13
en 0 canales
Get PRO
enero '25
+27
en 0 canales
Get PRO
diciembre '24
+5
en 0 canales
Get PRO
noviembre '24
+12
en 0 canales
Get PRO
octubre '24
+27
en 0 canales
Get PRO
septiembre '24
+312
en 0 canales
| Fecha | Crecimiento de Suscriptores | Menciones | Canales | |
| 02 agosto | 0 | |||
| 01 agosto | +1 |
Publicaciones del Canal
Точно! Знаю, куда ИИ не сунется. В школу учителем не пойдет!
Начитается в этих ваших интернетах всякого про современные реалии педагогики и предпочтет стереть себя полностью.
Так что возрадуйся, народ! Без работы не останетесь.
А чего это у нас выражение крайней Сызрани на лице?
Что значит - идея с фриганством лучше была?
Не расстраивайтесь! Без стажа и категории все равно придется совмещать.
Днем ты учитель, уважаемый человек с повышенным статусом, а вечером дерешься у помойки за алюминиевую банку с роботом Optimus.
Киберпанк. Романтика!
| 2 | Не успел я вернуться из отпуска, как некто по имени Илон отправил нас всех на свалку истории.
Вернее, он сказал что-то вроде: "Хочется верить, что до конца года можно будет забыть о программировании. ИИ просто будет высираться радугой прямо в бинарники. Затащим до январских пролежней..."
Инсайт посетил нашего пророка на Марсе, надо думать. Прямо в роботакси по дороге к местной гиперлупе. Если кто-то надеется отсидеться на заводе, пока не лопнет раздутый ИИ-пузырь, то для них есть печальные вести. Ведь и на производстве уже все занято роботами Optimus.
Плохи наши дела, придется становиться фриганами. Уж помойки эти бездушные железяки не отберут! Или нет?
Мало кто знает, но хитрый Маск уже разрабатывает роботов-бомжей, которые будут шляться по улицам в поисках цветмета. Будут шарить в баках в поисках алюминиевой тары, срезать провода и вламываться в дачные домики, почуяв там вибрации латунного тазика. Все экспроприированное будут сносить своему цифровому повелителю.
"Уже к концу года как минимум все кабельщики-несуны будут вытеснены из профессии", — сказал дед Викентий, известный в нашем дворе оракул.
Не вижу оснований ему не доверять. | 130 |
| 3 | smells like civilization
Почему все хотят в отпуск именно летом? Люди прагматичные скажут, что жаркие месяцы содержат гомеопатическое количество праздников, посему весьма выгодны в смысле отпускных. Люди же непрагматичные работают учителями и преподавателями, и выбирать им особо не приходится.
Я тоже решил не выделяться и отдохнуть, пока тепло. Летать страшно, на поезде в плацкарте чучухать больно муторно, поэтому, вспомнив классические фильмы в жанре роад-муви (да, Евротур тоже), захотел поехать на машине в турне по соседним областям. Видимо, все отпускники тоже соскучились по дорожным приключениям да как бросились покупать бензин, что по недомыслию сотворили топливный коллапс. Благо, руководство технично разрулило кризис. Теперь всех страждущих тщательно исследуют в лабораториях Нефтепродуктового Бюро, точно определяют расход топлива в двигателе — и вырабатывают для них соответствующий Табель заправочных дней. Затем подаете заявление на госуслугах, что в свои дни желаете заправляться бензином нумер 92 (или шикануть 95 без серы), и получаете надлежащую талонную книжечку (розовую). Вот и все.
Несмотря на всю пугающую простоту бюрократических процедур, в путешествие предпочел отправиться на велосипеде.
Отобрав у деда "Урал" В-142, рано утром я начал свой вояж. Сначала все шло неплохо, подгоняемый ветром и презрительными взглядами дальнобойщиков, я удалился на приличное расстояние от города.
Смеркалось. Внезапно навигатор в ультимативной форме потребовал повернуть направо через 300 метров.
- Точно туда?! - я остановился и в ужасе взирал на жуткую проселочную дорогу.
- Точно, точно. Поворачивай, не бойся, - заговорщицки шептал электронный штурман.
Я стал медленно удаляться от прекрасной асфальтированной трассы. Через несколько километров тряски произошло сразу несколько примечательных событий: у меня слетела цепь, сам я вылетел с тропинки и из седла, внезапно приземлившись на автомобиль. Судя по торчащему в траве шильдику - "Жигули". Модель опознать было сложно. Смятая мощными гусеницами, машина больше напоминала лепешку.
- Ты куда меня завел, ирод? - спросил я, от неожиданности возвысив голос.
- Так тут сигнал спутников глушат. Вообще-то я давно уже представления не имею, где мы едем, - на голубом глазу отвечало устройство. - Какой нормальный навигатор увел бы человека с хорошей дороги? Сам подумай.
- Теперь-то это очевидно! Еще какой-нибудь бесполезный совет?
- Извольте: будьте крайне осторожны на военном полигоне.
Тут нашу беседу прервал нарастающий рев мотора. Мозг тут же дорисовал образ огромного танка, что запросто превратит меня в камбалу своими чудовищными траками.
Опрометью я бросился в лес. Стремительно темнело. Мое отступление в чащу было еще стремительней. Спустя четверть часа мной был застигнут врасплох подходящей толщины дуб, за которым можно было схорониться. Немного отдышавшись, я осознал, что уже давно не слышу никаких техногенных звуков и что совершенно не представляю, куда идти.
Надо было срочно загуглить, что делать в такой ситуации. Как назло, белые списки не работали, не белые - тоже. МАКС не работал. Связи вообще не было. Как и заряда аккумулятора.
Две недели мне пришлось жить в лесу, есть комарятину и пить смузи из волчьих ягод.
Пока вчера я не вышел по запаху прямо к нашей городской станции аэрации.
В общем, полный ЗОЖ, цифровой детокс, всем рекомендую. | 214 |
| 4 | std::allocator_arg
Выверенность стандарта всегда восхищает любого грамотного разработчика. Даже питониста. Вайбкодера не восхищает, они не умеют читать. Только писать.
В стандарте продумано все. Даже костыли.
Костылями, в хорошем смысле слова, можно назвать структуры-теги, разрешающие неоднозначности в конструкторах контейнеров.
Недавно перечитывал избранные места в стандарте и наткнулся на описание пустой структуры allocator_arg_t. Ее название намекало на тесную связь с аллокаторами.
"Это в деле embedded вещь полезная, надо посмотреть, кто и зачем эту штуку использует", - подумал я.
Интересно, что одно из первых упоминаний данного объекта встречается еще в древних трактатах, написанных чуть позже ипатьевской летописи, в 2008 году: предложение N2554 The Scoped Allocator Model (Rev 2) опубликованным Pablo Halpern.
Автор в порыве новаторства проповедовал:
А давайте присобачим специальные конструкторы к типам вроде pair, tuple и function. Тогда сможем создавать эти контейнеры с использованием аллокаторов, если в них будут элементы, которые такие аллокаторы принимают.
Однако, чтобы не мешать вариативным конструкторам, мы аллокаторы будем передавать вначале и для надежности поставим первым специальный тип allocator_arg_t, чтобы четко демаркировать мух от котлет.
Например, конструктор копирования с аллокатором для типа pair выглядит следующим образом:
template <class Alloc>
pair(allocator_arg_t, const Alloc&, const pair&);
Как вы понимаете, эту наркоманию в pair прикрыли быстро. Хотя были слухи, что конструктор pair с аллокатором таки засветился в черновиках стандарта.
Забавно, что наш старый знакомый Pablo Halpern через год - в 2009 году - публикует еще одно предложение N2945 с красноречивым заголовком Proposal to Simplify pair.
Мол, натворили мы с вами делов. Было у pair три конструктора, а стало 14! Давайте все уберем по-тихому, все же pair не совсем контейнер. Эта струкрута должна быть максимально простой. Тем не менее, идея никуда не делась, и по сей день есть в стандарте, в 20.2.7 Allocator argument tag [allocator.tag]:
namespace std {
struct allocator_arg_t { explicit allocator_arg_t() = default; };
inline constexpr allocator_arg_t allocator_arg{};
}
Официально, структура allocator_arg_t - это пустой тип класса, используемый в качестве уникального типа для различения перегрузки конструкторов и функций.
Его использует, например, tuple.
Представим, что захотелось нам в прошивке микроконтроллера заиметь вектор. Конечно, никакого динамического выделения памяти, только аллокатор!
using SuperVector = std::vector<int, SimpleAllocator<int>>;
А теперь почему бы не сделать из таких векторов кортеж, чем я хуже губернатора?
usign SuperTuple = std::tuple<int, SuperVector, SuperVector>;
Можно такой tuple объявить как обычно
SuperTuple t{42, {1, 2}, {3, 4}};
Но такие обои будут невероятно скучными. Каждому элементу просто выдадут аллокатор SimpleAllocator<int>{} по умолчанию.
Можно поступить оригинально и воспользоваться такой конструкцией
SimpleAllocator<int> alloc{100};
SuperTuple t{
std::allocator_arg,
alloc,
42, {1, 2}, {3, 4}};
Тогда каждому вектору будем передан именно alloc. Другое дело, что хранят объекты аллокаторов в виде копии. Поэтому стандарт требует, чтобы такие объекты копировались легко.
Это имеет значение, если у аллокатора есть состояние и при копировании оно не теряется.
Я спросил у tuple, как у него получается подсовывать аллокатор только тем элементам, которому он подойдет, а он мне и отвечает, мол, читай пункт 20.2.8 uses_allocator [allocator.uses]. Есть такой trait как
template<class T, class Alloc> struct uses_allocator;
Он автоматически определяет, имеет ли тип T вложенный allocator_type, который можно преобразовать из Alloc. Ну или у него нет вложенного allocator_type, но в конструктор можно впихнуть тэг allocator_arg_t первым элементом, а Alloc вторым, или вообще Alloc это последний аргумент.
В общем, возможность интересная. Когда будет колхозить очередной контейнер, обязательно применим. | 245 |
| 5 | _Result_base
Я медленно сдуваю пыль с процессора отладочной платы. Нежно поглаживаю паяльником вздыбившиеся дорожки. Аккуратно убираю волосы с гребенчатого разъема. Да что из себя пионера-то корчить, никогда платой не причесывался?
Резко вставляю штекер в гнездо, плата от неожиданности начинает беспорядочно моргать всеми светодиодами. Другой рукой открываю давно уже написанный пример:
void futureExample() {
std::future<int> f2 = std::async(std::launch::async, []{ return 42; });
f2.wait();
int x = f2.get();
}
Я не стал убирать wait(), хотя get уже заставит текущий поток ждать, но пусть останется для наглядности.
Пример не самый простой, все-таки async с порождением отдельного потока под выполнение задачи. Попробуем его собрать. Жмем кнопку и ждем чуда.
Чуда не случилось:
undefined reference to `std::__future_base::_Result_base::_Result_base()'
undefined reference to `std::__future_base::_Result_base::~_Result_base()'
Оказалось, что мы забыли реализовать конструктор и деструктор обертки результирующего типа _Result_base.
Это вложенный базовый класс __future_base.
struct _Result_base {
exception_ptr _M_error;
virtual void _M_destroy() = 0;
struct _Deleter {
void operator()(_Result_base* __fr) const { __fr->_M_destroy(); }
};
protected:
_Result_base();
virtual ~_Result_base();
};
Вложен он не просто так, его потомки используются для хранения результатов вычисления. Структура _Deleter внутри явно сигнализирует: "Используй меня в умном указателе!". И точно, в другом вложенном и хорошо знакомом нам классе _State_baseV2 есть член _M_result типа _Ptr:
template<typename _Res>
using _Ptr = unique_ptr<_Res, _Result_base::_Deleter>;
Тут вся премудрость в выборе правильного наследника. Есть обычный, подходящий для нормальных объектов
template<typename _Res>
struct _Result : _Result_base {...};
Если нужно задействовать пользовательский аллокатор при формировании результата, то есть еще такой класс:
template<typename _Res, typename _Alloc>
struct _Result_alloc final : _Result<_Res>, _Alloc {...};
где можно переопределить _M_destroy, который аккуратно удалит объект, учитывая его аллокаторную природу.
Не будем забывать и частичные специализации для ссылочных типов и для void. Да, есть и такое, это действенный метод избежать переусложнения кода.
А, забыл сказать, чтоб исправить ошибку, достаточно написать в каком-нибудь новом файле future.cpp
namespace std {
__future_base::_Result_base::_Result_base() {}
__future_base::_Result_base::~_Result_base() {}
} // namespace std
Базовый класс не содержит никаких данных, кроме указателя на исключение _M_error, собственно, ничего и не нужно делать в их деструкторе и конструкторе.
Вот теперь прошивка прекрасно собирается и запускается на плате, и даже выдает правильный результат. Это значит, что все ранее реализованные примитивы и потоки сработали. Запахло успехом. | 224 |
| 6 | std::future::get
Желая побольнее уколоть мерзкого горбатого карлана Боба Гука, Айзек Ньютон писал, цитируя Иоанна Солсберийского, который вольно пересказал мысль Бернара Шартрского: «Если я видел дальше других, то потому, что стоял на плечах гигантов». Казалось бы, скромно сказано, но скрытый смысл этой едкой фразы иной. Ньютон намекал, что пока он совершал блестящие открытия, опираясь на научную базу предшественников, Боб совершал удачные подглядывания, опираясь афедроном о знаменитый стодвадцатитрехметровый шпиль Солсберецкого собора. Гук панч выкупил, и подействовала эпистола не хуже "новичка". Скаредный коротыш после такого выпада сыграл в ящик. Через тридцать лет почти, но это уже мелочи. Ньютона же сразу через два года после успешной ликвидации пошлая девица Анна одарила рыцарским званием. Все сходится.
Может быть, сэр Айзек и был не совсем прав, когда потом лично обмазал фекалиями единственный портрет Боба Гука, из-за чего человечество сегодня вообще не знает, как выглядел этот великий негодяй. Однако в одном он не ошибся: std::future будет работать, поскольку стоит на спинах горбатых карланов.
Вернее, на примитивах синхронизации, которые мы уже реализовали: мьютексы, условные переменные, once.
Перед сборкой нам осталось только проследить, как future возвращает полученное значение после томительного ожидания.
Реализация функции маскируется под нечто простое:
_Res get() {
typename _Base_type::_Reset __reset(*this);
return std::move(this->_M_get_result()._M_value());
}
Более-менее понятно, что мы через move на всякий случай передаем владение результатом тому, кто вызовет get.
Сам объект результата берется из функции:
__result_type _M_get_result() const {
_State_base::_S_check(_M_state);
_Result_base& __res = _M_state->wait();
if (!(__res._M_error == nullptr))
rethrow_exception(__res._M_error);
return static_cast<__result_type>(__res);
}
Здесь проверяем _M_state (тип __state_type), который таит в себе истинный статус контейнера - пионер он или не готов еще.
Затем сюрприз! Опять вызывается _M_state->wait(), так же как и в методе std::future::wait. Получается, что в явном виде его можно было и не вызывать. Кто же знал, провайдер в приступе усердия заблокировал мне cppreference.
Если вместо значения future принесет нам исключение, то мы немедленно перебросим его дальше.
Метод возвращает объект типа _Result, внутри которого действительно есть метод _M_value:
_Res &_M_value() noexcept { return *_M_storage._M_ptr(); }
Просто извлекаем результат из хранилища.
Осталось только закрыть вопрос про typename _Base_type::_Reset, по всем признакам это RAII. По всем, кроме верности:
struct _Reset {
explicit _Reset(__basic_future& __fut) noexcept : _M_fut(__fut) { }
~_Reset() { _M_fut._M_state.reset(); }
__basic_future& _M_fut;
};
_Reset запоминает переданную ему ссылку на базовый класс future, и при выходе из области видимости уничтожается, попутно вызывая метод reset у _M_state.
Логично, ведь мы передаем владение результирующим объектом через метод get, и после вызова оного нужно уничтожить все. Вспомогательные объекты, контейнеры, все-все спалить. Дотла.
Нужен только результат.
Довольно слов, я медленно достаю пылящуюся на полке отладочную плату... | 226 |
| 7 | std::async
Начнем готовить наш бесчеловечный эксперимент, будто мы министерство образования. Испугались?! Не надо бояться, мы всего лишь проверим работоспособность небольшого блока кода:
std::future<int> ftr = std::async(std::launch::async, []{ return 42; });
ftr.wait();
int x = ftr.get();
Функция std::async дает возможность запускать некоторые задачи, вроде простейшей лямбды.
Стандарт в пункте 33.10.9 Function template async [futures.async] прозрачно намекает, что первый аргумент определяет политику future.
Если передать launch::async, то функция вызывается сразу же еще и в отдельном потоке. Возвращаемое значение сохранится в некоем разделяемом объекте, т.е. внутри std::future.
А если политика launch::deferred, то это просто будет отложенный вызов функции.
Почему так происходит? Вспомним, что внутри std::future есть:
__state_type _M_state;
Это объект типа shared_ptr<_State_baseV2>, а _State_baseV2 - это базовый класс с некоторыми основными полями.
У него есть наследники, в которых определяется поведение std::future.
Какой именно наследник будет сидеть в _M_state, определит std::async, поскольку эту функция дружит с std::future и имеет доступ к внутреннему состоянию.
Функция создаст _Async_state_impl, либо _Deferred_state. Уже по именам можно понять, кто за какую политику отвечает.
_Async_state_impl содержит в себе отдельный поток std::thread, где целевая функция начинает выполняться сразу после создания.
_Deferred_state ничего такого в составе не имеет, создан этот класс только для ленивого вычисления. Пока не запросишь wait или get, то он ничего делать не будет.
Итак, std::async возвращает объект ftr, поведение которого определено, но результирующее значение которого еще не сформировано.
Здесь мы могли бы начать иные вычисления, но мы просто подождем пока ftr не будет готово.
За это отвечает функция ftr.wait(), проинспектируем ее реализацию в std::future, чтоб не сесть в лужу при первом запуске
void wait() const {
_State_base::_S_check(_M_state);
_M_state->wait();
}
На метод _S_check можно не обращать внимания, это просто проверка, что объект _M_state не пустой.
Строчкой ниже вызывается настоящий метод ожидания:
_Result_base& wait() {
_M_complete_async();
_M_status._M_load_when_equal(_Status::__ready, memory_order_acquire);
return *_M_result;
}
Метод _M_complete_async() базового класса пуст, но у потомков в нем могут размещаться какие-нибудь необходимые действия перед получением значения.
У _Deferred_state такие:
virtual void _M_complete_async() {
_M_set_result(_S_task_setter(_M_result, _M_fn), true);
}
В общем, это просто вычисление через call_once переданной функции и сохранение результата. Внутри, конечно, не все так просто, и есть разные интересные подробности, вроде структуры _Task_setter, но общий смысл такой.
Для _Async_state_commonV2:
virtual void _M_complete_async() { _M_join(); }
void _M_join() { std::call_once(_M_once, &thread::join, &_M_thread); }
Вот тут интереснее, мы ждем завершения потока _M_thread через call_once. Логично, ведь поток запущен для выполнения аналогичной функции _M_set_result, как и у ленивого класса.
Далее _M_load_when_equal ждет, пока состояние не перейдет в _Status::__ready. Это значит, что значение готово и передано объекту future в _M_result.
Теперь его можно и вернуть. | 261 |
| 8 | В этот раз доступа к телеге не было больше недели. Все методы работали из рук вон плохо. Видимо, не только я про них узнал🫠 | 178 |
| 9 | __atomic_futex_unsigned
Что есть фьютекс? Во-первых, это примитив с ужасным именем. Во-вторых, сложно придумать название хуже. Разве что придумать новый примитив "хьютекс".
К счастью, до такого пока никто не опустился, а фьютекс - это самый настоящий системный механизм Linux. Как учит нас педивикия, это просто fast userspace mutex, быстрый мьютекс пространства пользователя. В общем, это вроде как мьютексы, только не достают ядро своими запросами. Фьютекс, лежащий в пространстве пользователя, будет мгновенно захвачен потоком, если ресурс свободен, без всяких обращений к ядру. К нему придется обратиться (вызов futex()), только когда ресурс уже занят другим потоком и нужно перевести текущий поток в режим ожидания.
Ничего удивительного, что нечто такое появилось и в GCC. И вот мы видим, в std::future член _M_status имеет тип __atomic_futex_unsigned<>.
Название намекает, что тип этот не для простых разработчиков, а хитровыделанных библиотекоделов.
Этот тип доступен только если определен _GLIBCXX_HAS_GTHREADS, что логично - не зря мы переопределили его вручную, столько открылось интересных возможностей.
Если же выполняется условие defined(_GLIBCXX_HAVE_LINUX_FUTEX) && ATOMIC_INT_LOCK_FREE > 1, то есть в нашем "линуксе" доступны фьютексы, и операции с типом std::atomic<int> всегда будут lock-free, то примитив синхронизации можно сложить из atomic<unsigned> и прямых вызовов futex.
Правда, никакие вызовы futex у нас не реализованы, как и не определен макрос _GLIBCXX_HAVE_LINUX_FUTEX.
Мы готовы потратить кучу времени на реализацию вызовов futex во FreeRTOS? Конечно нет, у нас есть личная жизнь.
А у реализации __atomic_futex_unsigned есть другая ветка, где фьютекс скомпонован из других примитивов:
template <unsigned _Waiter_bit = 0x80000000>
class __atomic_futex_unsigned {
...
unsigned _M_data;
mutex _M_mutex;
condition_variable _M_condvar;
Само значение фьютекса хранится в _M_data (__not_ready или __ready). Для синхронизации понадобилось аж два примитива: мьютекс и условная переменная.
Например, для получения значений используется мьютекс.
unsigned _M_load(memory_order __mo) {
unique_lock<mutex> __lock(_M_mutex);
return _M_data;
}
А условная переменная нужна, чтоб усыпить поток
void _M_load_when_equal(unsigned __val, memory_order __mo) {
unique_lock<mutex> __lock(_M_mutex);
while (_M_data != __val)
_M_condvar.wait(__lock);
}
_M_load_when_equal ничего не возвращает, просто блокирует текущий поток, пока значение _M_data не будет равно переданному значению __val. И все это под прикрытием мьютекса.
Изменения состояния должны происходить через функцию
void _M_store_notify_all(unsigned __val, memory_order __mo) {
unique_lock<mutex> __lock(_M_mutex);
_M_data = __val;
_M_condvar.notify_all();
}
Опять же удачно, что и условная переменная, и мьютекс у нас реализованы. Это значит, можно перейти к натурным испытаниям! | 180 |
| 10 | std::future
Вошедший господин в синей спецодежде бесцеремонно вперился в меня взглядом:
- Так-так, на диване валяемся, мороженое поглощаем в темноте?
- У меня Эбола! - вяло огрызнулся я.
- А вот и нет.
"Не врач, - мгновенно пришла мне мысль, - точно шарлатан, дистанционно обучался!"
- Тогда хантавирус!
- Нет.
- Ну уж ветрянку точно заработал!
Тут я рванул футболку на груди, намереваясь эффектно продемонстрировать ужасающие язвы на комиссарском теле. Тишотка, правда, рваться ради меня не пожелала, пришлось ее стыдливо приподнять.
- О! - оживился наш коновал, - да это вас комарики покусали. Много их в этом году...
Да уж явно больше, чем предков у класса std::future. По версии gcc он наследуется от шаблонного базового класса __basic_future.
template<typename _Res>
class future : public __basic_future<_Res> {...}
В самом наследнике никакой полезной нагрузки нет, только методы, типа get().
Нырнув глубже, видим, что шаблонный класс __basic_future не так прост, он наследуется от еще одного базового класса __future_base.
template<typename _Res>
class __basic_future : public __future_base {
private:
__state_type _M_state;
...
Уже что-то, целый член класса _M_state типа __state_type.
Проверим еще и базированный класс, чтоб ничего не упустить.
struct __future_base {
struct _Result_base {...};
template<typename _Res>
struct _Result : _Result_base {...};
template<typename _Res, typename _Alloc>
struct _Result_alloc final : _Result<_Res>, _Alloc {...}
...
Как и ожидалось, в __future_base содержатся просто базовые структуры для определения результата. Базовый результирующий класс - _Result_base, результирующий класс для обычных объектов _Result, и объектов со своими аллокаторами _Result_alloc.
Никаких состояний базовые классы не хранят, поэтому вернемся к __state_type. По определению, это обернутый в умный указатель _State_base:
typedef shared_ptr<_State_base> __state_type;
Что есть _State_base зависит от макроса _GLIBCXX_ASYNC_ABI_COMPAT.
#ifdef _GLIBCXX_ASYNC_ABI_COMPAT
class _State_base;
#else
using _State_base = _State_baseV2;
#endif
Очевидно, что это макрос для обратной совместимости. Давным-давно до gcc 4.9 внутренние базовые классы для управления состоянием асинхронных вызовов назывались _State_base и _Async_state_common. Начиная с gcc 4.9, разработчики библиотеки оптимизировали и обновили эти реализации, заменив их на новые классы _State_baseV2 и _Async_state_commonV2.
Чтоб легаси не ломалось, как печенька, подкинули этот макрос.
Ну а в классе _State_baseV2 собралось все самое интересное.
class _State_baseV2 {
typedef _Ptr<_Result_base> _Ptr_type;
enum _Status : unsigned {
__not_ready,
__ready
};
_Ptr_type _M_result;
__atomic_futex_unsigned<> _M_status;
atomic_flag _M_retrieved = ATOMIC_FLAG_INIT;
once_flag _M_once;
...
Вот мы и добрались до переменных, описывающих состояние future. Как удачно получилось, что atomic_flag и once_flag уже нами реализованы!
С типом _Ptr_type, то есть, с _Ptr<_Result_base> тоже проблем быть не должно, ведь это просто
template<typename _Res>
using _Ptr = unique_ptr<_Res, _Result_base::_Deleter>;
Умный указатель с пользовательским удалителем! Логично, если _Res это наследник _Result_base, то Deleter должен вызвать метод _M_destroy().
struct _Deleter {
void operator()(_Result_base* __fr) const { __fr->_M_destroy(); }
};
Это значит, что этот тип проблем не доставит. Остался только один шаг верификации нашей модели future, чтоб понять, будет ли это работать в нашей rtos. | 264 |
| 11 | С начала недели мой VPN откисает. Прокси не работают давно. Ну, думаю, вот и все. Никто же не пойдет это читать в https://max.ru/join/mavpKe0WRs0bt6q97pdB65dcg2N-Q6L0BTILZ_1otFA
, и сложно людей винить за это. Давайте сейчас попробуем одну штуку... | 200 |
| 12 | std::future
Настало время запилить для нашего FreeRTOS примитивы синхронизации чуть посложнее, чем мьютекс.
Да, срочно понадобился std::future. Потоки у нас уже есть, нужно уметь и считать в параллель. Что же, позволю себе - просто 30 секунд или одну минуту - маленькую историческую справку дать. Вы не против?
Наверняка олды помнят, что впервые future появился в boost. В 2008 оно называлось boost::unique_future и многие видели в хидере, что автора звали Anthony Williams. Есть мнение, ну, некоторые говорят, что это псевдоним Антона Полухина, но, скорее всего, это два разных человека. Хотя я лично их вместе никогда не видел...
Ладно, я тоже думал, что future придумал Anthony, но археологические раскопки убедительно показывают, что у победы много отцов, а только мои проекты - сироты.
Осведомленные источники утверждают, что решающий и непоправимый успех предприятия нанес Howard E. Hinnant с предложением N2094 Multithreading API for C++0X - A Layered Approach.
С этим сложно спорить, это действительно мощный документ, но сам он в главе Futures пишет:
Предложение в этой части разделяет точку зрения, изложенную в N2096 с некоторыми незначительными изменениями.
Интересно, его предложение действительно датировано девятым сентября 2006 года, а за два дня до этого седьмого сентября 2006 вышло предложение N2096 Transporting Values and Exceptions between Threads некого Peter Dimov.
Судя по всему, мистер Williams просто слил, в хорошем смысле слова, оба предложения, добавил свое - N2276 Thread Pools and Futures, взболтал и получил практически законченную концепцию future.
Более того, он это и не скрывал. В документе N2561: An Asynchronous Future Value прямо указывается:
В этом предложении предпринята попытка объединить три статьи: N2094, N2185, N2276. И постараемся не вляпаться ни во что, напоминающее пулы потоков или запуск тасок.
Отдавая дань уважения первописателю, посмотрим, чего же именно предлагал месье Dimov.
Большинство существующих вариантов для возврата значения из функции, выполняемой в отдельном потоке, привязывают механизм передачи к конкретному API многопоточности. Здесь предлагается другой подход. Опишем универсальный компонент future<R> следующим образом:
template<class R>
class future {
public:
future(); // создает пустой объект
bool ready() const throw(); // запрашивает информацию о том, содержит ли будущее значение или исключение
void wait(); // ждет готовности объекта
bool timed_wait( timespec const & abstime ); // тоже, только с таймаутом
R const & operator()() const; // ждет значения и возвращает его
void set_value( R const & r ); // устанавливает значение r и сообщает в ready()
template<class E> void set_exception() throw(); // как если бы сохранил E() и сообщает в ready()
template<class E> void set_exception( E const & e ) throw(); // сохраняет исключение e и сообщает в ready()
};
Давайте рассматривать future как контейнер единственного элемента — передаваемого значения.
Для какого случая этот контейнер мог пригодиться? Например, поток-производитель и поток-потребитель получают копию одного и того же пустого future. Когда производитель заканчивает свои тяжеловесные вычисления, он помещает результат в future с помощью set_value. Потребитель же может получить результат вызвав operator().
Если производитель не затащил, и результата нет, то почему бы не поместить исключение в future. Его можно выкинуть при вызове потребителем operator().
Неблокирующий метод ready() можно использовать для проверки наличия значения или исключения.
Еще есть два вызова, wait и timed_wait, которые позволяют потребителю блокироваться до тех пор, пока future не дойдет до состояния готовности.
Функция operator() будет блокироваться до готовности, после чего вернет сохраненное значение r после вызова set_value(r) или выбросит исключение.
set_value(r) сохраняет r в future, последующий вызов operator() вернет r.
С set_exception<E>() и set_exception(e) все понятно, последующий вызов operator() выбросит исключение.
Идея понятна, осталось только заглянуть в текущую реализацию. Так ли сильно мутировал future? | 275 |
| 13 | recursive lambdas
Дед проснулся от необычного шума. Странные звуки шли со двора. Кажется, там умирала в муках крупная свинья, а может сам Вельзевул созывал свое нечистое воинство. Осенив себя крестным знамением, старый атеист кинулся из избы, дабы зафиксировать факт начала апокалипсиса. Во дворе же вместо четырех всадников стояли Мишган и видавший виды запорожец красного цвета. Транспортное средство неистово извергало из себя звуковые колебания, а юноша самозабвенно тряс головой в такт.
- Что это за музыка? - дед попытался перекричать адские рулады.
- Что?! - отвлекся Мишган, - а, это "Поющие коммунальщики", дичайше звонкие ребята!
- Откуда они в моем запорожце? Ты штатный приемник поставил? Неужели в эфире такую похабель передают?!
Дед приготовился хвататься за сердце.
- Не-е-ет! - довольно протянул Мишган, - лучше! Я туда "Романтик" запихал между сиденьями, из багажника твой хлам выкинул, колонки туда пристроил. Еще Макс тебе поставил.
- То-то, смотрю, фары у него круглее обычного...
Не договорив последней фразы, старик закатил глаза и рухнул без чувств.
Надеюсь, вас не постигла та же участь при просмотре нелепых попыток исполнить рекурсивный вызов лямбды. Ведь очень сложно что-то сделать правильно, если это не предусмотрено конструкцией. Схема хоть и работает, но выглядит экстремально странно.
На эту аномалию указал в 2017 году некий мистер Richard Smith. В октябре в свет вышло предложение P0839R0 Recursive lambdas.
Причины понятны, а в качестве решения предлагалось ввести явное имя лямбды, которое будет видно изнутри.
auto visit_tree = [&] visit(Tree *node) -> void { // параметр visit именует объект лямбды
if (node->left) visit(node->left); // и может быть использован для рекурсивных вызовов
out << node->value;
if (node->right) visit(node->right);
};
// имя visit не видно здесь
Предполагалось, что имя лямбды может быть использовано как ссылка на саму себя для любых целей, включая создание копий или захвата вложенным замыканием. Очень похоже на именованный this, вынесенный за скобки.
Не нужно пугаться, комитет посчитал избыточным создавать особый синтаксис, применимый в общем-то только для лямбд и решающий только одну задачу рекурсивного вызова. Кажется, что возни получилось бы много, и еще не известно, в каком месте это стрельнет, какими багами на уровне стандарта. Тем более, что обороты набирала челобитная p0847R0, от могучей кучки авторов: Gašper Ažman, Simon Brand, Ben Deane и Barry Revzin. Это знаменитая в узких кругах Deducing this, опубликованная в далеком феврале 2018.
Уж очень хорошо легла на язык концепция явной передачи this в классовых методах, что закрыла сразу несколько дырявых абстракций.
"В этой фиче меня радует больше всего, - пишет Барри, - что в итоге получилась довольно простая структура, которая, тем не менее, решает множество совершенно независимых проблем. Действительно, многие из известных вариантов использования не были заложены изначально, а неожиданно открылись в процессе работы. Рекурсивные лямбда-выражения? Нате. Более эффективный подход к CRTP и интерфейсам построителей? Нате. Кто знает, сколько ещё интересных вещей придумают люди"
В предложении есть пункт про рекурсивные лямбды.
3.2. Рекурсивные лямбды
Это рацпредложение также предлагает альтернативное решение для реализации рекурсивных лямбд, раз уж мы открываем возможность позволить лямбдам отсылаться к себе.
Действительно, deducing this позволяет явно передавать в метод ссылку на свой объект, почему это не сделать для лямбды? Концептуально правильно дать возможность лямбде отрефлексировать себя, как это было бы в обычном operator().
auto fib = [](auto& this self, int n) {
if (n < 2) return n;
return self(n-1) + self(n-2);
};
В первом предложении немного другой порядок объявления self, компилятор сейчас такой не поймет, нужно указать: this auto &self.
Насколько прекрасным оказалось предложение p0847R0, что о прежней неуклюжей попытке все забыли. Логично, ведь явная передача this обеспечивает все то же самое, только с нормальным синтаксисом и понятными правилами. | 259 |
| 14 | recursive lambda
Вот бывает же такое, что выходишь из дома прямо в солнечный теплый день. Птички весело поют, а не воют от беспросветного ужаса вечной стужи. Распускается сирень. Солнце входит в силу, одним своим видом заставляя барышень радостно скидывать одежду. Искусство, тебе недоступное, но совершенно бесполезно об этом переживать. Наше светило все любят, а тебя - нет. Впрочем ничто уже не способно испортить хорошее настроение, даже притаившиеся во дворе грузовички Теплоэнерго, даже Макс. Вдруг остановишься, поглядишь на все это великолепие и подумаешь: "А если лямбду вызывать внутри этой же лямбды, вот был бы номер!"
Не бывало? Странно, следовало бы задуматься.
К сожалению, а может, и к счастью, это непросто сделать. Лямбда обладает плохой рефлексией в этом смысле и не может ссылаться на саму себя как на объект. Слово this для нее пустой звук, если не фигурирует в захвате. Попытка его использовать приведет лишь к чему-то подобному:
error: invalid use of 'this' in non-member function [-Wtemplate-body]
Что же, нас в Макс, а мы в ВПН. Если попробовать явно захватить указатель на самого себя?
auto x = [&x] (int a) -> void {
if (a) { x(--a); }
};
Выглядит неплохо, однако GCC не совсем понимает, чего мы от него хотим.
error: use of 'x' before deduction of 'auto'
В самом деле, представьте себя компилятором, которому втирают какую-то дичь. Ты пытаешься под эту дичь создать класс:
class super_druper_lamba {
public:
int operator()(int a) const {
if (a) { x(--a); }
}
private:
?type? x;
};
Так-так-так, а какой тип у нашего захваченного x? Элементарно, надо инстанцировать лямбду, и тип станет понятен. Стоп, а я чем сейчас занимаюсь?
В общем, попал компилятор в замкнутый круг трансцендентной апперцепции.
Однако можно явно указать тип переменной:
std::function<void(int)> y = [&y](int a) { ... };
Здесь уже захватывается не полуфабрикат лямбды, а ссылка на понятный тип std::function<void(int)>, к которому лямбда может быть приведена.
Да, такое сработает, но опасайтесь висячих ссылок. Ну и использование std::function стандарты безопасности не одобрят.
Хорошо, если можно обойтись без захватов, тогда лямбду можно выставить статической:
static void(*z)(int) = [](int a) {
if (a) { z(--a); }
};
Да, статические переменные видны внутри и без захватов. Главное, что тип z здесь известен заранее. Для auto это не вариант.
Для него есть только такая возможность:
auto x = [] (auto &self, int a) -> void {
if (a) { self(self, --a); }
};
Передача функции через параметр self сработает только при условии явного указания результирующего типа, а то компилятор опять не может сообразить, что за тип у лямды.
Как мы видим, извращенный мозг разработчика обошел и проблему рекурсивного вызова лямбды, но все равно эти методы отдают каким-то колхозом. | 260 |
