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 словно Хатико. О, это отдельная поучительная история, полная драмы.
"Как убеждать людей в своей правоте без конфликтов..." - читаю я небольшой плакатик над полкой. Опускаю взгляд и вижу ровную шеренгу книг Карнеги "Как заводить друзей и оказывать влияние на людей".
"Вот это бы мне пригодилось бы, - думаю, аккуратно вынимая пособие, - авось, перестану в коментах на ревью ругаться, коллеги ко мне потянутся..."
Я быстро пробежался по аннотации, где в конце было приписано, что впервые сей труд издали в 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 словно Хатико. О, это отдельная поучительная история, полная драмы.