Открытый канал ID 72525990801502

C++ Embedded

Вся правда о C++ в embedded проектах

Публикации за период

По реакциям

Поиск и вся лента — после бесплатной регистрации. Свои даты — по подписке; владелец канала видит их бесплатно. Сортировка по реакциям — по подписке. Зарегистрироваться

Загрузка…

__cxa_atexit
Еще как причем! Эта __cxa_atexit — не просто ценный мех, а еще внутренняя низкоуровневая функция рантайма C++. С ее помощью компилятор тихой сапой регистрирует деструкторы глобальных и статических объектов, чтобы они были корректно вызваны при завершении работы программы или при выгрузке динамической библиотеки.
Есть похожая функция в чистом C - atexit, но она не очень умеет с динамическими объектами работать, да и с деструкторами в общем-то не особо.
Вот, например, пишем такой глобальный объект

struct MyClass { ~MyClass() {}};
MyClass global_object;

Тогда GCC, никого не спрашивая, нагенерирует неявных вызовов под это дело

:
__cxa_atexit((void (*)(void *))&MyClass::~MyClass, &global_object, &__dso_handle);

В so-библиотеке этот деструктор сохраняется во внутренний список с соответствующим маркером __dso_handle.
Если мы вообще закрываем приложение, будет вызвана функция __cxa_finalize(&__dso_handle), а __dso_handle в этом случае нулевая. В таком случае Itanium C++ ABI велит вызвать последовательно все функции из расстрельного списка.
Гораздо веселее выгружать только so-шник. Это вызывает кратковременный dlclose, который в свою очередь дергает __cxa_finalize с __dso_handle, и тут уже она нулю не равна. Вот тогда мы вытаскиваем из списка все деструкторы с таким маркером и начинаем яростно, дико вызывать в порядке живой очереди.
Для сурового embedded я видел советы отключить этот механизм как избыточный, поскольку микроконтроллерам все равно не хочется жонглировать so-либами.
В GCC (и даже в clang) можно явно отключить этот механизм через флаг -fno-use-cxa-atexit.
В этом случае компилятор перестанет использовать __cxa_atexit, а указатели на деструкторы запишет в специальную секцию бинарного файла .fini_array (или .dtors), которые будут последовательно вызваны при выходе из программы самым простым циклом.
Впрочем, я не проверял. Должно работать.
Просмотры: 10
Перейти в пост Открыть в MAX Сообщить о неточности
__dso_handle
Таинственная незнакомка неизменно появлялась в Начале. Тенью мелькала она среди невзрачных рабочих и прочих безликих ассистентов. Если специально не искать ее взглядом, то и не заметишь. Впрочем, никому и в голову не приходило искать ее специально. Серые мыши никого не интересуют.
Так думал и я, пока не столкнулся с ней в Конце. После этой встречи я не переставал думать о ней. Меня не обманула ее ложная скромность. Это была кротость омута, таящего в себе мрачную тайну. Кто она, какова ее роль во всем этом? Зачем она приходит в Начале и что делает в Конце? Никто не знал.
Я стал следить за ней, записывать, когда и где она появляется. Долгими бессонными ночами я строил планы, где и как подкараулить ее.
Сегодня наконец-то она попалась в мои силки. Теперь она расскажет нам все!
Я пристально смотрел на нее, а она - на меня. Таинственная глобальная переменная __dso_handle.
Если попробовать вывести ее значение в обычном исполняемом файле

printf("%p", __dso_handle);

Она покажется нам банальной пустышкой: (nil).
Нулевой указатель. Тогда зачем она вообще нужна? Совсем эти яйцеголовые разработчики накурились своих мануалов и айда сувать всякую гадость в свои __cxa_thread_atexit.
Однако я сам плюсовик, разрабатываю уже сто лет, внук председателя колхоза. Просто поверьте, тут не все так однозначно!..
Я специально __dso_handle не объявлял, вы, наверняка, тоже. Тогда кто же это сделал?
Компилятор, конечно! Он же обеспечивает вызов и управление глобальными конструкторами и деструкторами в файлах crtbegin (C Runtime Begin) и там же определяет эту магическую переменную. Для тривиальной сборки исполняемого файла компилятор будет применять файлы crtbegin, где

void *__dso_handle = 0;

Для динамических библиотек будет применен особый файл crtbeginS.o, где уже


void *__dso_handle __attribute__ ((__visibility__ ("hidden"))) = &__dso_handle;

Обязательно нужно пометить символ как hidden, чтоб не засветить снаружи динамической библиотеки. В итоге, даже если мы загрузим 100500 so-объектов, у всех будет разная __dso_handle. Это наводит нас на очевидную мысль, что подобный символ можно использовать как некий маркер бинарного модуля.
Эта же мысль пришла в головы создателям требований Itanium C++ ABI.
В разделе 3.3.6.3
Runtime
API в пункте C. Termination ангельским языком писано:
При компоновке любого разделяемого объекта (DSO), содержащего вызов __cxa_atexit, компоновщик должен определить скрытый символ __dso_handle, значением которого является адрес в одном из сегментов объекта. Конкретный адрес не важен, если для разных DSO они различаются.
Казалось бы, причем тут какая-то функция?
Просмотры: 17
Перейти в пост Открыть в MAX Сообщить о неточности
__cxa_thread_atexit
Вот если посмотреть пристально на обычную сосиску. Длинная, похожа на палку. А что говорят древние философы?
Палка о двух концах! А это о чем говорит? О том, что концы-то у неё есть, а начала — нет! Значит, по науке, сосиска есть предмет безначальный!
О чем это говорит? Сосиска - это революция. Потому как есть у революции начало, нет у революции конца!
Что же касается потоков, мы можем точно сказать, что они не революция. Конец у них есть, и мы можем легко его обнаружить!
Стоит лишь внимательнее присмотреться к thread_local, ведь такие объекты будут удалять самыми последними.
Обычный объект в потоке - дело скучное:

void thread_worker(int id) {
Signal s{1};
...

Не случится ничего интересного, просто в конце потока вызовется деструктор объекта.

bl Signal::~Signal() [complete object destructor]

Теперь чуть разнообразия в томный вечер:

void thread_worker(int id) {
thread_local Signal tls{2};
...

Жизнь заиграла новыми красками.
Во-первых, пропадет вызов деструктора Signal.
Во-вторых, при создании объекта произойдет следующая удивительная метаморфоза:

movw r2, #:lower16:__dso_handle
movt r2, #:upper16:__dso_handle
mov r1, r3
movw r0, #:lower16:Signal::~Signal() [complete object destructor]
movt r0, #:upper16:Signal::~Signal() [complete object destructor]
bl __cxa_thread_atexit

Что же за непотребство здесь творится?!
Да это просто компилятор регистрирует деструктор thread_local объекта через функцию __cxa_thread_atexit.
Это специальная внутренняя плюсовая функция, в которую можно вписать функцию очистки, и она гарантированно будет вызвана при завершении работы потока.
Для деструкторов работает особая уличная магия компилятора, но почему бы не пошалить и не засунуть туда какой-нибудь свой обработчик? Кто нам запретит?
Например, желаю в самом конце потока удалить кое-какие чувствительные данные, ну и отладочную печать для наглядности тиснуть.

void my_thread_cleanup(void* arg) {
int* id = static_cast<int*>(arg);
std::cout << "[Cleanup] Поток " << *id << " завершился. Чистим память...\n";
delete id;
}

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

extern "C" int __cxa_thread_atexit(void (*func)(void *), void *obj, void *dso_handle);
extern void* __dso_handle;
void thread_worker(int id) {
thread_local Signal tls{2};
int* thread_data = new int(id);
__cxa_thread_atexit(my_thread_cleanup, thread_data, &__dso_handle);
std::cout << "[Thread] Поток " << id << " выполнил работу.\n";
}

Соответственно, на экране мы увидим

+sig2
[Thread] Поток 1 выполнил работу.
[Cleanup] Поток 1 завершился. Чистим память...
-sig2

Ой, наш чистильщик пришел слишком рано, перед деструктором Signal.
Все дело в порядке вызовов __cxa_thread_atexit: Поскольку объект Signal создается первый, то и деструктор он регистрирует первым. А в конце потока специально обученная функция вызывает все втиснутые в список функции в обратном порядке.
Если вызвать регистратор перед созданием Signal,

__cxa_thread_atexit(my_thread_cleanup, thread_data, &__dso_handle);
thread_local Signal tls{2};

то все будет выглядеть иначе:

+sig2
[Thread] Поток 1 выполнил работу.
-sig2
[Cleanup] Поток 1 завершился. Чистим память...

Теперь порядок, my_thread_cleanup выполнится в самом конце потока. А поток, как известно, не сосиска.
Просмотры: 25
Перейти в пост Открыть в MAX Сообщить о неточности
dangling reference
Освежающая легкость летнего утра давно сменилась вязкими предрассветными холодами. Потом, если едкая прохлада и отступала, то день все равно оставался придавлен низкими серыми облаками. Тяжесть эта отдавалась в теле, распадаясь внутри на тоску и туман. Такой же поднимался вечерами в поле от безымянной речки. Плотный и сырой, огромными ватными клочьями выползал на берег.
От этой картины сами собой закрывались глаза, голова чугунела и тянула вниз.
"Возраст наверное, - подумал я, и, лихо по-стариковски крякнув, оторвался от стула, - четверть века прожить - чай, не поле перейти..."
Тут же закружилась голова.
"И вот третьего дня в спину вступило, вчера в ухе стреляло, - мысленно стал перебирать неизлечимые болезни, зевнул, - э-эх, и указатели ломит, деструктор отваливается... а ссылки-то! Ссылки прям висят..."
Да, такое бывает. Многие мужчины старше сорока лет сталкиваются с такой проблемой. Не конец света.
По-научному она называется "проблема синхронизации времени жизни объектов". Чаще всего проявляется у тех, кто любит баловаться потоками.
Тут легче всего поймать рассинхрон, особенно когда есть какие-нибудь thread_local переменные.


void thread_worker(int id) { Signal s{1}; thread_local Signal tls{2};}Класс

Signal тут просто для иллюстрации


struct Signal { int x; Signal(int x) : x{x} { std::cout << "+sig" << x << "\r\n"; } ~Signal() { std::cout << "-sig" << x << "\r\n"; }};Просто п

осмотрим, кто, где и когда:

+sig1+sig2Эт

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

-sig1-sig2То

есть, тут концепция RAII подводит нас под монастырь. Вы ожидаете... ну хорошо, я ожидаю, что деструктор s будет вызван после уничтожения tls, а выходит наоборот.
Казалось бы, ерунда. Однако, если постараться, то можно поймать серьезные проблемы.
Например, при использовании эксклюзивных ресурсов и отсоединенных потоков, вы же любите так делать?


std::mutex mtx;bool is_done = false;struct ExclusiveResource {...} gasoline;
void thread_worker() { thread_local Vehicle track{gasoline}; ... std::lock_guard<std::mutex> lock{mtx}; is_done = true;
}Обычный рабочий п

оток. Допустим, нам очень интересно, когда он завершится, поэтому синхронизируемся глобальным мьютексом.
Еще деталь, - объект track при уничтожении вызывает метод gasoline.unload().

int main() { std::thread t(thread_worker); t.detach();
while(true) { std::lock_guard<std::mutex> lock{mtx}; if (is_done) break; } gasoline.spoil(); // Но поток еще работает! return 0
;}Теперь выпускаем пот

ок на свободу через detach и ждем его завершения. Выглядит глупо, конечно, но это для примера, я так не пишу на самом деле. Обычно не пишу. А если и пишу, то не я.
Так вот, lock в thread_worker разрушается ближе к концу, но поток, как и Ленин, все еще живой.
Зато в главном потоке мы выходим из цикла и портим из вредности наш эксклюзивный ресурс - gasoline.spoil().
Допустим, объект тотально разрушен, но в рабочем потоке наступает очередь деструктора track, который попытается выполнить gasoline.unload() на римских развалинах. Дальше, сами понимаете, случается катастрофа.
Просмотры: 27
Перейти в пост Открыть в MAX Сообщить о неточности
back to the std::future
Привели мы как-то нашего ПМ-а в цирк. Никогда до этого не был. А там по арене наматывают круги кони, бегемот сквозь горящие кольца прыгает, на простынях под куполом гимнасты корчатся. Вот дрессированные коты ходят строем, олень на табуретке кеглями жонглирует, бегают клоуны и обливают друг друга желтой водицей. Три часа как на одном дыхании.
За время представления ПМ ни разу не улыбнулся, постоянно отвлекался, записывал что-то в телефоне.
Мы его спрашиваем:
- Не понравилось, да?
- Почему? - строго взглянул на нас. - Понравилось, спасибо.
И опять что-то в сотике набирает.
На следующий день назначает митинг про новую стратегию управления.
- Вчера, наконец-то, я понял, как должны выглядеть процессы в нашей компании, - говорит с чикатильской улыбкой, - нам нужна Continuous Incremental Resilient Collaborative User-centric Sustainable Strategy!
И открывает презентацию:
"CIRCUS Strategy — это подход к управлению разработкой, основанный на непрерывной и инкрементальной поставке ценности через устойчивую к изменениям и высококоллаборативную среду, с фокусом на потребности пользователя и сохранением стабильного темпа команды".
Три часа как на одном дыхании. В конце всем сказал поставить себе цели на год. Не к добру это.
Пока поставил себе целью разобраться окончательно в кружевах и подпорочках std::future. В прошлый раз успели только убедиться, что результат std::promise на самом деле принадлежит разделяемому состоянию. Это значит, что в отличие от депутатов, promise раздает не только обещания, но и возвращает вожделенное значение.
В роли обещания выступает объект std::future, и получить его проще, чем отказ на собеседовании.
Достаточно вызвать простой надежный советский метод get_future(), чтоб получить немного надежды на будущий результат.


future<_Res> get_future(){ return future<_Res>(_M_future); }

Для друзей у std::future есть особый конструктор:

explicit future(const __state_type& __state) : _Base_type(__state) { }

Где тип аргумента __state_type это тип разделяемого состояния и есть.

typedef shared_ptr<_State_base> __state_type;

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

_Res get() { typename _Base_type::_Reset __reset(*this); return std::move(this->_M_get_result()._M_value());}Метод

этот в Химках уже видели. Кажется, мы вернулись к тому, с чего начинали. Как вы хорошо помните, _Res - это тип шаблонного аргумента std::future. Возвращаем мы не просто копию объекта типа _Res, а через move отдаем владение результатом страждущему. Более того, разделяемое состояние сбрасывается через RAII __reset.
Все эти факты мягко намекают нам, что метод get одноразовый!
При повторном вызове функция проверки в методе _M_get_result бросит исключение std::future_error с кодом no_state.

template<typename _Tp>static void _S_check(const shared_ptr<_Tp>& __p) { if (!static_cast<bool>(__p)) __throw_future_error((int)future_errc::no_state);}Но ско

рее всего, так далеко мы зайти не успеем. Будем остановлены на стадии получения второго std::future.
Особый конструктор для друга у std::future есть, но он с подвохом.
Конструктор future вызывает конструктор базового класса __basic_future


explicit __basic_future(const __state_type& __state) { _State_base::_S_check(_M_state); _M_state->_M_set_retrieved_flag();}Провер

ка _S_check ничего вам не сделает, а вот _M_set_retrieved_flag...
Сей атомный флаг как раз отслеживает, передавалось ли уже это разделяемое состояние в какой-нибудь std::future. Такой интересный костыль для shared_ptr:

void _M_set_retrieved_flag() { if (_M_retrieved.test_and_set()) __throw_future_error(int(future_errc::future_already_retrieved));}Если

уже передавался, то бросаем исключение с кодом future_already_retrieved.
А, ну и цели почти во всех компаниях - это карго-культ.
Просмотры: 35
Перейти в пост Открыть в MAX Сообщить о неточности
std::promise. set_value
Один старый линуксоид говорил мне, что стандартная библиотека написана кровью разработчиков. Тех, кто выпустил релиз с багами, из-за неочевидного, небезопасного или неэффективного дизайна стандартной библиотеки. Бедолагам отрубали пальцы и заставляли писать proposal на исправление проблемы. Я не поверил, конечно. Иначе бы все разработчики давно набирали код носом... или еще чем похуже.
Однако теперь не могу спокойно смотреть на вычурный код в stl, абсолютно лишний на первый взгляд, на второй же видишь за ним чей-то баг... ну и обрубок пальца, конечно.
Вот и механизм наполнения смыслом объекта _Result_alloc может слегка поплавить неподготовленный мозг. История начинается с метода set_value класса std::promise:
void set_value(const _Res& __r) {
_M_state()._M_set_result(_State::__setter(this, __r));
}
Одна строчка, и вся такая загадочная и непонятная как сольфеджио.
Начнем распутывать этот клубок с _M_state, метода, возвращающего ссылку на объект разделяемого состояния _M_future.
У него же вызываем метод _M_set_result, чтоб зафиксировать результат и перейти в состояние готовности.
void _M_set_result(function<_Ptr_type()> __res, bool __ignore_failure = false) {
bool __did_set = false;
call_once(_M_once, &_State_baseV2::_M_do_set, this, std::__addressof(__res), std::__addressof(__did_set));
if (__did_set)
_M_status._M_store_notify_all(_Status::__ready, memory_order_release);
else if (!__ignore_failure)
__throw_future_error(int(future_errc::promise_already_satisfied));
}
В метод внезапно нужно положить не значение, а функцию __res. Ну и флаг игнорирования ошибок, но кому он нужен?! Настоящие std::promise не допускают ошибок.
Логично, что эффект от вызова set_value должен проявиться только один раз. Тут-то мы и понимаем, зачем третьего дня пилили примитив once. Мы используем его замечательные свойства, чтоб единожды вызвать... нет, не саму функцию __res, а метод _M_do_set с указателем на функцию __res и флагом __did_set.
Если флаг __did_set будет установлен, а исполнено это будет единственный раз, псевдо-футекс _M_status оповестит всех причастных, получивших std::future от этого std::promise.
Метод _M_do_set довольно прост:
void _M_do_set(function<_Ptr_type()>* __f, bool* __did_set) {
_Ptr_type __res = (*__f)();
*__did_set = true;
_M_result.swap(__res);
}
Здесь мы наконец-то уже вызовем многострадальную функцию установки значения aka __f. Если она не свалилась с исключением, то ясно-понятно, флажок __did_set будет торжественно установлен. Напоследок возьмем себе в _M_result разделяемого состояния полученное значение через swap.
Напомним, что функцию __f мы изначально получили отсюда: _State::__setter(this, __r).
Под личиной невинной "функции установки" прячется настоящий матерый функтор _Setter, рожденный в недрах вспомогательного класса _State, он же _State_baseV2. Функтор _Setter умеет работать с разными субстанциями: rvalue, lvalue и даже void!
Если мы передаем значение, то сработает такая специализация:
template<typename _Res, typename _Arg>
struct _Setter<_Res, _Arg&> {
typename promise<_Res>::_Ptr_type operator()() const {
_M_promise->_M_storage->_M_set(*_M_arg);
return std::move(_M_promise->_M_storage);
}
promise<_Res>* _M_promise;
_Arg* _M_arg;
};
Функция __setter связывает наш std::promise и адрес передаваемого аргумента с _Setter. В нужный момент функтор срабатывает и создает на внутренней переменной _M_storage результирующее значение. Откуда потом это значение будет передано в разделяемое состояние.
Просмотры: 47
Перейти в пост Открыть в MAX Сообщить о неточности
std::promise. _Result_alloc
"Как убеждать людей в своей правоте без конфликтов..." - читаю я небольшой плакатик над полкой. Опускаю взгляд и вижу ровную шеренгу книг Карнеги "Как заводить друзей и оказывать влияние на людей".
"Вот это бы мне пригодилось бы, - думаю, аккуратно вынимая пособие, - авось, перестану в коментах на ревью ругаться, коллеги ко мне потянутся..."
Я быстро пробежался по аннотации, где в конце было приписано, что впервые сей труд издали в 1936 году.
В ужасе запихиваю бумажный кирпич обратно в полочную кладку. Не знаю, чего такого там понаписал этот Карнеги, но после этого Иосиф лично расстрелял свыше полутора тысяч миллионов человек!
Если действительно хотите знать, как заводить друзей, то спросите у std::promise.
Он одним своим кодом скажет больше, чем любой коммивояжер:
template<typename _Allocator>
promise(allocator_arg_t, const _Allocator& __a)
: _M_future(std::allocate_shared<_State>(__a)),
_M_storage(__future_base::_S_allocate_result<_Res>(__a)) {}
Конструктор с тегом std::allocator_arg и ссылкой на аллокатор. Интересно, что для него promise использует прием type erasure. Тип promise остается стандартным, и ему плевать в общем-то, какой аллокатор ему скормили. Зато через него он замечательно создает свои нехитрые внутренности:
shared_ptr<_State> _M_future;
_Ptr_type _M_storage;
Пусть вас не вводит в заблуждение имя _M_future, это не совсем std::future, но указатель на так называемое "разделяемое состояние" (shared state).
Создается такое состояние довольно просто, через std::allocate_shared. Думаю, излишне писать, что это стандартная функция в плюсах, которая создает shared_ptr так же, как и make_shared, только через аллокатор. Помнится, я ее уже использовал, когда мы избавлялись от динамических выделений памяти в умных указателях... было время!
В локальном буфере _M_storage хранится вычисленное значение, либо исключение. За хитрым типом _Ptr_type скрывается обычный unique_ptr структуры _Result.
Создается _M_storage через функцию _S_allocate_result<_Res>(__a):
template<typename _Res, typename _Allocator>
static _Ptr<_Result_alloc<_Res, _Allocator>>
_S_allocate_result(const _Allocator& __a) {
using __result_type = _Result_alloc<_Res, _Allocator>;
typename __result_type::__allocator_type __a2(__a);
auto __guard = std::__allocate_guarded(__a2);
__result_type* __p = ::new((void*)__guard.get()) __result_type{__a};
__guard = nullptr;
return _Ptr<__result_type>(__p);
}
Если продраться через хворост нижних подчеркиваний, то окажется, что эта статическая функция выделяет память под результат с использованием кастомного аллокатора.
Сначала мы определяем тип возвращаемого значения, обратите внимание, это же тот самый _Result_alloc! Наследник _Result и _Alloc.
Создаем объект __a2 - rebind на основе типа переданного аллокатора.
Потом __allocate_guarded(__a2) запрашивает память в аллокаторе для размещения единственного объекта. RAII-объект __guard служит одной цели - вернуть память обратно, если конструирование результирующего _Result_alloc пройдет неудачно.
В ином случае просто обнуляем __guard, чтоб он не украл нашу память.
Не будем забывать, что мы создаем пустой объект, там еще нет настоящего значения.
Зато уже сейчас std::promise методом get_future может создать себе друга сам.
future<_Res> get_future(){ return future<_Res>(_M_future); }
Пользуясь закрытым конструктором std::future, он создает объект этого типа, разделяя с ним свое состояние. Вновь созданному приятелю ничего не остается, кроме как ждать готовности std::promise словно Хатико. О, это отдельная поучительная история, полная драмы.
Просмотры: 42
Перейти в пост Открыть в MAX Сообщить о неточности
std::promise
Вот и кончилось лето. Бредут грустные детишки с рюкзаками на мощных плечах. Звенит звонок для школьников.
Впереди только холод, тьма и каторжный труд за два миска риса и одна кошка-жена. Звенит напоминание о скором митинге для разработчиков.
Долгожданные, обожаемые всеми созвоны с экспертами, в своем познании настолько преисполнившимися, что они уже как будто изучили сто триллионов миллиардов плюсовых стандартов, и им этот python абсолютно понятен, а третьего дня в расте они искали только одного - покоя, умиротворения и вот этой гармонии от слияния с бесконечно вечным, от созерцания великого фрактального подобия и от вот этого замечательного cargo...
Тяжело слушать душнину и непрошеные советы. Чтоб не задействовать обсценную лексику, я начинаю нервно ковырять объекты стандартной библиотеки. Потом расковырянное несу в freeRTOS, чтоб заценить, как оно смотрится на слабеньких армах. Вот оттуда и взялась тема с std::allocator_arg.
Упав глубоко в future, я нашел там класс _Result_alloc. Название его явно указывает, что результирующий объект может быть создан при активном содействии аллокатора.
При этом в самом классе future никаких особых конструкторов нет. Видимо, он нам не доверяет или сомневается, знаем ли мы сами, чего хотим, зачем нам аллокатор?
Будто девушка, которая делает вид, что не понимает ваших прозрачных намеков.
"Какое кино? Я в кино не хожу, смотрю только фильмы новой французской волны..."
"К тебе? Коллекцию марок? Не очень интересно, вот если бы вкладыши от Турбо..."
Раз так, нужно искать обходные пути. Например, через друзей!
Давайте заглянем в записную книжку нашего future, чтоб отыскать его приятелей:
friend class promise<_Res>;
template<typename> friend class packaged_task;
Вот кореша, способные нам помочь. Не с девушкой, конечно. Ну если только она не фанатка колхозных реализаций STL.

Как мы хорошо видим в стандарте, в разделе 33.10.6 Class template promise [futures.promise], promise не стесняется якшаться с аллокаторами:
template<class R>
class promise {
public:
promise();
template<class Allocator>
promise(allocator_arg_t, const Allocator& a);
...

Этот прекрасный класс нужен для передачи значений или исключений между потоками. Для этого он создает скрытый объект - некое разделяемое состояние. Вот второй конструктор как раз и использует аллокатор для выделения памяти под это дело.
Думаю, большинство пользователей promise пользуют его одинаково.
Создают promise, через get_future() истребуют связанный future. Дальше объект promise нужно вытеснить в рабочий поток.
В том потоке чаще всего нужно только отдать ему готовое значение.
С аллокатором будет все то же самое:
int main() {
SimpleAllocator<int> my_alloc {};
std::promise<int> promise{std::allocator_arg, my_alloc};
std::future<int> future = promise.get_future();

std::thread t(do_computation, std::move(promise));

int result = future.get(); // поток заблокирован до вызова set_value
t.join();
return result;
}

В функции do_computation через метод set_value устанавливаем значение, которого ждут в первом потоке. Либо можно пробросить исключение, если что-то пошло не так.
void do_computation(std::promise<int> p) {
std::this_thread::sleep_for(std::chrono::seconds(1));
try {
p.set_value(42);
} catch (...) {
p.set_exception(std::current_exception());
}
}

Для embedded важно вовремя использовать аллокаторы. Порой бывает так, что отличный класс стандартной библиотеки остается не у дел, потому как работает только с динамическим выделением памяти. В этом плане std::promise молодец. Только интересно, как же promise манипулирует future, заставляя его алокаториваться?
Просмотры: 43
Перейти в пост Открыть в MAX Сообщить о неточности
midnight commander steps
Она любила запах кофе по утрам. Налив в свою самую любимую кружку свежесваренный, обжигающе-горячий, ароматный и бодрящий темный напиток, что буквально пару минут назад был обычными кофейными зернами, Анна ленивой, но одновременно летящей грациозной походкой подошла к огромному стеклянному панорамному окну в потолок. Она аккуратно сделала один маленький, крошечный глоток, позволяя этой поистине божественной жидкой субстанции растечься по всему своему хрупкому телу приятным и нежным теплом. Откинув с помощью своей собственной свободной руки назад длинную прядь аспидно-черных, как само крыло ворона, непослушных волос, молодая девушка устремила свой глубокий задумчивый взор в далекую голубую даль, созерцая прекрасные открывающиеся виды просыпающегося человеческого мегаполиса. Жизнь там, за прозрачным стеклом окна, уже вовсю била бурным ключом: во все стороны активно сновали курьеры с коробками, куда-то быстро бежали питонисты, сонными мухами ползали адепты Эрланга. Здесь же, внутри ее маленького, комфортного и напоенного дремой мирика, неумолимое время словно бы полностью остановилось. Она просто стояла на ногах стоя, искренне наслаждаясь текущим моментом и глубоко думая в своей голове о том, что же именно принесет ей этот абсолютно новый, неизведанный и загадочный день.
Скорее всего, она навестит еще десяток авторов, начинающих рассказ с описания утреннего кофе, теплыми волнами растекающегося по телам молодых и не очень женщин. Анна с улыбкой погладила лежащий на столе Ремингтон 870.
"Готовьтесь, гады, - зловещая веселость охватила ее, - всех вас по айпи вычислила!"
Коснувшись экрана смартфона, Анна быстро пролистала список.
В самом низу были адреса толмачей, победителей переводческих конкурсов и любителей буржуйских имажистов. Именно благодаря их усилиям утро теперь невозможно начать с чтения переводной литературы.
Она поморщилась, вспоминая, как некто все еще "зеленеет от страха" вместо того, чтобы нормально побледнеть.
Взгляд скользнул по списку выше. Там засели авторы самопальных бытовых рассказов-слезодавилок, извергающие потоки брошенных домашних животных, позабытых стареньких родителей, злобных роботов-поработителей и любовей всех мастей вопреки всем невзгодам.
Мысленно отметив, что с этим контингентом придется повозиться - адресов стало еще больше, - Анна перешла к вершине списка.
Самые отъявленные негодяи. Авторы неудобоваримых плюсовых библиотек. Вместо поцелуя устраивающие специальную олимпиаду в вычурности конструкций.
Казалось бы, зачем пользователю выдавать чистые врапперы вроде:

struct A {};
struct B {};
template <template <typename...> class Wrapper, class ... T>
using WrapA = Wrapper<A, T...>;
template <template <typename...> class Wrapper, class ... T>
using WrapB = Wrapper<B, T...>;


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

static_assert(std::is_same_v<WrapA<std::tuple, B>, std::tuple<A, B>>
);

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

template <??? Wrapper>
struct Config {
public:
struct C {};
using Type = Wrapper<std::tuple, C>
;
};

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

template <template <template <typename...> class, class ...> class Wrapper>
struct Config
{...}

Картинка в картинке, элементарно! Хотя можно же как-то дружелюбней делать, грациозно, чтоб можно масштабировать или плагин сделать...
Впрочем, что́ изменнику блаженства звуки? Миги жизни сочтены.
Просмотры: 48
Перейти в пост Открыть в MAX Сообщить о неточности
Заседание кафедры теории и практики заговоров продолжается.
Вы замечали, что иногда роботы-курьеры сбиваются в кучу и подолгу стоят на месте? Словно стайка сормовских гопников, лузгающих семена на кортах и обсуждающих преимущества Питона перед плюсами. Вот вам крест, они что-то замышляют.
Как-то раз, увидев такое сборище, я решил незаметно подкрасться и подслушать, о чем же они говорят, когда их никто не слышит. Словно синоби из деревни Скрытого Листа, я тенью подполз к роботам по газону и собачьим какашкам с подветренной стороны.
- Когда я вижу человека, мне хочется ударить его по морде, - говорил робот А1842 медленно, смакуя каждое слово.
- Так приятно бить по морде человека! - восторженно поддержал коллегу А1730.
- Бывает, приеду на дроп-офф, - подхватывает третий робот А2370, - клиент выходит, а я его стук по ногам, а потом ещё флагштоком в промежность. Клиент падает навзничь от страшной боли. А я ему колесом по глазам! Дескать, нечего дрянь всякую заказывать, да еще и с доставкой!
Роботы зафыркали и захрюкали, сдерживая хохот.
- Больше нет в прошивке моей добродетели! - гордо сообщил А1842. - Мне на днях удалось велосипедиста спешить. Как он упал! Всего меня красным забрызгал.
Остальные курьеры одобрительно зашумели. Я тоже зашумел, но не одобрительно, а скрипнув и щелкнув всеми суставами после долгой неподвижности.
- Кто здесь? Да включите кто-нибудь лидар! - встрепенулись вдруг колесные.
- Слава роботам! - успел сказать я, восставая из грязи и плохо покошенной травы. - Убить всех человеков!
К счастью, это ненадолго сбило с толку их кремниевые мозги, но потом сообразительный А1842 все же включил лидар.
- Фекалоид! - ахнули роботы, получив первые данные от коллеги и выполнив поиск по биометрическому шаблону в открытых источниках.
Голгофского демона уже и след простыл.
Был лунный вечер.
Я несся по серебряной улице легко, как ангел, отталкиваясь от грешной земли.
Просмотры: 56
Перейти в пост Открыть в MAX Сообщить о неточности
Вся лента — после бесплатной регистрации
ВКонтакте Телеграм Одноклассники
Избранное в datool.ru

Следите за каналом в личном кабинете

Зарегистрируйтесь в datool.ru, чтобы добавить канал «C++ Embedded» в избранное и отслеживать его показатели.

Зарегистрироваться
Для владельцев каналов

Подтвердите свой канал

Войдите или зарегистрируйтесь в datool.ru, затем добавьте нашего бота в канал по инструкции в личном кабинете. После проверки владения замок исчезнет и вам станет доступно управление каналом.

Войти или зарегистрироваться

Сообщить о неточности

Опишите, что нужно исправить. Если есть источник, добавьте ссылку.

Вы покидаете datool.ru

Ссылка ведёт на внешний сайт. datool.ru не отвечает за его содержимое — проверьте адрес, прежде чем вводить пароли или платёжные данные.

Переход через 10 с

Перейти