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 результирующее значение. Откуда потом это значение будет передано в разделяемое состояние.
Один старый линуксоид говорил мне, что стандартная библиотека написана кровью разработчиков. Тех, кто выпустил релиз с багами, из-за неочевидного, небезопасного или неэффективного дизайна стандартной библиотеки. Бедолагам отрубали пальцы и заставляли писать 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 результирующее значение. Откуда потом это значение будет передано в разделяемое состояние.