Здравствуйте, Evgeny.Panasyuk, Вы писали:
V>>Где ты предлагаешь разместить аналог своего asio::coroutine в future<>?
EP>Там же где и обычное обычное замыкание-продолжение.
EP>Абстрагируйся от asio::coroutine, представь простое замыкание-продолжение. Например у нас вместо:
EP>EP>future<int> sum()
EP>{
EP> int x = await foo();
EP> int y = await bar();
EP> return x + y;
EP>}
EP>
есть ручное нарезание на продолжения:
EP>EP>future<int> sum()
EP>{
EP> return foo().then([](int x)
EP> {
EP> return bar().then([=](int y)
EP> {
EP> return x + y;
EP> });
EP> });
EP>}
EP>
Где по-твоему здесь размещаются эти продолжения?
Оба раза в куче. ))
Т.е. лямбда создаётся на стеке, но при сохранении её в std::function, происходит создание её копии в куче, потому что объект function устроен как-то так (схематично):
template<>
class function<void()>
{
public:
template<typename T>
function(const T & closure)
: closure_(makeClosure(closure))
{}
void operator()() {
closure_->invoke();
}
private:
struct Closure {
virtual void invoke() = 0;
};
template<typename T>
static Closure * makeClosure(const T & closure)
{
struct ClosureImpl {
T closure_;
virtual void invoke() { closure_(); }
};
return new ClosureImpl() {closure};
};
Closure * closure_;
};
Реальное исполнение отличается от приведенного попыткой оптимизации для маленьких замыканий, типа как для обычных адресов ф-ий, чтобы, если std::function создаётся от аргумента-простого адреса ф-ии, ничего в куче не создавать.
EP>И почему stackless корутина должна размещаться в каком-то другом месте?
Потому что, как и в случае std::string, для которого тоже есть некая оптимизация "маленьких" значений, реальный размер корутины заранее предугадать невозможно. Собсно, открой <functional> и посмотри на кол-во вызовов new.
V>>Я бы его разместил в объекте-наследнике того самого shared_state, который, в свою очередь, создаётся на куче.
EP>А здесь можно вообще не менять ни сам future, ни лезть в его внутренности (типа наследования от shared_state). Потому что используем ли мы обычные продолжения, или объект-автомат (ручной или сгенерированный stackless coroutine) — это совершенно ортогонально тому где мы их размещаем.
Если используем future, то у нас в любом случае будет выделение памяти из кучи. Я лишь предлагаю свести кол-во выделений к минимуму, реализовав корутину как наследника future, либо как в случае с make_shared, просто расположив оба объекта в одном куске памяти (что фактически идентично с т.з. использования памяти). И я УВЕРЕН, что это именно так и будет. Даже могу поспорить на денюжку. )) Сорри, но это слишком очевидно, чтобы это "промухать". Не промухают, я уверен.
V>>Другое дело:
V>>V>>iterator<T> generate() resumable {
V>> yield return 1;
V>> yield return 2;
V>>};
V>>
V>>Т.к. тип iterator<> еще не специфирован, вполне возможно, что у него будет некое зарезервированное поле, в котором можно поместить целочисленную переменную — состояние автомата-корутины.
EP>Ты понимаешь что над корутинами из Boost.Asio можно сделать обёртку-итератор, в котором полностью будет хранится вся корутина? Или показать пример?
Не надо пример. Я, как раз, понимаю о чем речь, но ты не вникаешь в суть моих вопросов. Я всегда спрашиваю одно и то же — какой будет тип результирующего объекта? Можно ли объявить переменную этого типа БЕЗ объявления корутины, а реальную корутину присвоить "потом" — как в твоём сценарии сериализации?
V>>Если твоя легковесная корутина будет содержать в себе другие корутины, то нужно эмулировать стек для их правильного обхода. И тут уже, извини, но без выделения памяти в куче — никак.
EP>Зачем?
Для рекурсии. В т.ч. всевозможной неявной, через вызовы, скажем, захваченных в лямбду методов-переменных.
EP>Вложенная корутина будет просто полем объекта внешней корутины
Ты забыл, что корутина у нас stack-less, поэтому стек ей придётся нарисовать ручками. И этот стек — штука динамическая, увы))
Поэтому — в куче.
EP>Вот пример, в нём нет никаких аллокаций в куче
(скипнул пример)
В этом примере нет, ес-но. Потому что нет недетерминированного стека вызовов.