2308C++理解编译器转移

原文
介绍
设置场景
定义任务类型
1:确定承诺类型
2:创建协程状态
第3步:调用取中()
4:初挂起点
5:记录挂起点
第6步:实现协柄::恢复()协柄::消灭()
第7步:实现协柄<承诺>::承诺()从承诺()
8:协程体的开始
9:降级协待
10:实现对异常()
11:实现协中
12:实现终挂起()
13:实现对称转移无操协程(地址)
最后,绑定在一起

介绍

"了解C++协程"系列的先前博客讨论了编译器对协程及其协待,协产协中式等不同类型的转换.
协程理论
C++协程:了解协待符号这里
C++协程:了解承诺类型
C++协程:了解对称转移

设置场景

首先,假设有个既为可等待返回类型,又为协程返回类型的基本任务类型.为了简单,假设此协程类型允许异步生成.
本文,介绍如何降级以下协程函数到不包含协程协待/协中关键字的C++代码中,以便更好理解.

//其他函数的前向声明.实现不重要.
任务 f(整 x);
//转换为非`C++`代码的简单协程
任务 g(整 x){
    整 fx=协待 f(x);
    协中 fx*fx;
}

定义任务类型

首先,声明要用的任务类.
为了理解协程是如何降级的,不想知道该类型的方法的定义.降级会插入调用它.

类 任务{:
    构 等待器;
    类 承诺类型{:
        承诺类型()无异;
        ~承诺类型();
        构 止等待器{
            极 直接协()无异;::协柄<>挂起协(::协柄<承诺类型>h)无异;
            空 恢复协()无异;
        };
        任务 取中()无异;::总是挂起 初挂起()无异;
        止等待器 终挂起()无异;
        空 对异常()无异;
        空 中值(整 结果)无异;:
        友 任务::等待器;::协柄<>连续_;::变量<::单态,,::异常针>结果_;
    };
    任务(任务&&t)无异;
    ~任务();
    任务&符号=(任务&&t)无异;
    构 等待器{
        显 等待器(::协柄<承诺类型>h)无异;
        极 直接协()无异;::协柄<承诺类型>挂起协(::协柄<>h)无异;
        整 恢复协();:::协柄<承诺类型>协程_;
    };
    等待器 符号 协待()&&无异;:
    显 任务(::协柄<承诺类型>h)无异;::协柄<承诺类型>协程_;
};

1:确定承诺类型

任务 g(整 x){
    整 fx=协待 f(x);
    协中 fx*fx;
}

编译器发现此函数包含(协待,协产协中)三个之一的协程关键字时,它启动协程转换.
第一步是确定此协程要用的承诺类型.
这是按标::协征的模板参数替换返回类型和参数类型的签名来确定的.
如,对有任务返回类型和单个类型的参数的g函数,编译器使用标::协征<任务,整>::承诺类型查找它.
定义别名,以便稍后可引用它:

用__g承诺型=::协征<任务,>::承诺类型;

注意:用前双下划线来指示是编译器生成的内部符号,不应在用户代码中使用它.
现在,因为没有特化标::协征,会实例化仅按返回类型的嵌套承诺类型名的别名定义嵌套承诺类型的主模板.
即,示例中,应该按任务::承诺类型类型解析.

2:创建协程状态

协程函数需要在挂起时保留协程,参数和局部变量的状态,以便在恢复协程时仍可用.
标准C++中,此状态叫协程状态,一般由堆分配.
首先为g协程的协程状态定义一个结构.
还不知道其内容,暂时留空.

构__g状态{
  //待填弃
};

协程状态包含许多不同的内容:
1,承诺对象
2,函数参数副本
3,有关协程当前挂起的挂起点恢复/析构信息
4,生命期跨挂起点的局部/临时变量的存储

首先为承诺对象和参数副本添加存储.

构__g状态{
    整 x;
    __g_promise_t__承诺;
    //待填充
};

接着,应添加一个构造器初化这些数据成员.
(如果该调用有效)编译器试先用参数副本左值引用来调用承诺构造器,否则回退到调用承诺类型的默认构造器.
创建简单助手来帮助解决该问题:

<型名 承诺,型名...形参>
承诺 构造承诺([[也许未用]]形参&...形参){
    如 常式(::可构造从<承诺,形参&...>){
        中 承诺(形参...);
    }{
        中 承诺();
    }
}

因此,协程状态构造器可能如下:

构__g状态{
    __g状态(&&x)
    :x(静转<&&>(x))
    ,__承诺(构造承诺<__g承诺型>(x))
    {}
    整 x;
    __g_promise_t__承诺;
    //待填充
};

现在已有了表示协程状态类型的开始,也开始实现g()的降级,方法是给它传递函数参数,并堆分配__g状态类型实例,以便可复制/移动它们到协程状态.
"斜坡函数"指代协程实现的包含初化协程状态并准备好开始执行协程逻辑的一部分.即,它是进入执行协程体一个入口.

任务 g(整 x){*状态=新__g状态(静转<&&>(x));
    //...实现其余斜坡功能
}

注意,承诺类型未自定义新符号,所以在此只是调用全局::符号 新.
如果承诺类型确实自定义了符号,则它调用符号而不是全局::符号 新.首先检查,是否可用(大小,参数值…)参数列表调用符号.

如果,则用该参数列表调用它.否则,仅用(大小)参数列表来调用它.符号访问协程函数的参数列表的功能有时叫"参数预览",想为协程状态用按参数传递分配器分配存储时非常有用.

如果编译器发现__g承诺型::符号 新定义,则降级为以下逻辑:

<型名 承诺,型名...实参>*__承诺分配(::大小型 大小,[[也许未用]]实参&...实参){
  如 常式(要求{承诺::符号 新(大小,实参...);}){
    中 承诺::符号 新(大小,实参...);
  }{
    中 承诺::符号 新(大小);
  }
}
任务 g(整 x){*状态内存=__承诺分配<__g承诺型>(的大小(__g状态),x);
    __g状态*状态;{
        状态=::(状态内存)__g状态(静转<&&>(x));
    }(...){
        __g承诺型::符号 删(状态内存);;
    }
    //...实现其余的斜坡功能
}

此外,此承诺类型不定义分配失败上取中对象()静态成员函数.如果此函数是在承诺类型上定义的,则这里的分配应改用标::不抛型形式的符号,且在返回空针中 __g承诺型::分配失败上取中对象();.

即像这样:

任务 g(整 x){*状态=::(::不抛)__g状态(静转<&&>(x));(状态==空针){
        中__g承诺型::分配失败上取中对象();
    }
    //...实现其余的斜坡功能
}

为简单起见,后面只使用最简单的调用全局::符号 新的内存分配函数.

第3步:调用get_return_object()

斜坡函数做的下一件事是在承诺对象上调用取中()方法来取斜坡函数的返回值.
局部变量存储返回值,并在斜坡函数结束时(在其他步骤完成后)返回.

任务 g(整 x){*状态=新__g状态(静转<&&>(x));
    推导()中值=状态->__承诺.取中();
    //...实现其余斜坡功能
    中 中值;
}

但是,现在调用取中()可能会抛,此时,要释放分配的协程状态.因此,为了平衡,给状态的所有权加上标::独针,以便后续触发异常时释放它:

任务 g(整 x){::独针<__g状态>状态(新__g状态(静转<&&>(x)));
    推导()中值=状态->__承诺.取中();
    //...实现其余斜坡功能
    中 中值;
}

4:初挂起点

在调用取中()后,斜坡函数要做的下一件事是开始执行协程体,而协程体中的第一件事是初始挂起点.
即求值协待 承诺.初挂起().

现在,最好,只按初挂起对待协程,然后按恢复初挂起的协程实现启动协程的.但是,初始挂起点的规范在如何处理异常协程状态生命期方面有一些怪癖.
这是在C++20发布之前对初挂起点语义的后期调整,以修复此处的一些相关问题.
计算初挂起点中,如果从以下位置触发异常:
1,调用初挂起(),
2,在(如果定义了)调用返回的可等待协待()符号,
3,等待器上调用直接协(),或
4,等待器上调用挂起协()
则,异常传播回斜坡函数的调用者,并自动析构协程状态.
如果从以下位置触发异常:
1,调用恢复协(),
2,从(如果可以)协待()符号返回的对象析构器,或
3,从初挂起()返回的对象析构器
则,此协程体抓异常并调用承诺.对异常().

表明要小心处理这部分转换,因为一部分需要在斜坡函数中,而另一部分需要在协程体中.

此外,因为从初挂起()和(可选)协待()符号返回的对象有跨挂起点的生命期(在挂起协程点前创建,并在恢复协程后析构),因此要在协程状态存储这些对象.

此例,从初挂起()返回的类型是恰好是个空的,可平凡构造标::总是挂起简单类型.
但是,逻辑上讲,仍要在协程状态下存储该类型实例,因此为其添加存储,以展示工作原理.

只会在调用初挂起()这里构造该对象,所以要添加允许显式控制生命期的数据成员.
为此,先定义个人工生命期助手类,它是三元可构造的,也是可平凡析构的,但在需要时允许显式构造/析构存储在那里的值.

<型名 T>
构 人工生命期{
    人工生命期()无异=默认;
    ~人工生命期()=默认;
    //不可复制/移动
    人工生命期(常 人工生命期&)=;
    人工生命期(人工生命期&&)=;
    人工生命期&符号=(常 人工生命期&)=;
    人工生命期&符号=(人工生命期&&)=;<型名 工厂>
        要求
            标::可调用<工厂&>&&::相同于<::调用结果型<工厂&>,T>
    T&构造从(工厂 工厂)无异(::是可不抛调用值<工厂&>){*::(静转<*>(&存储))T(工厂());
    }
    空 消灭()无异(::是可析构不抛值<T>){::消灭在(::加载器(重转<T*>(&存储)));
    }
    T&()&无异{*::加载器(重转<T*>(&存储));
    }:
    对齐为(T)::字节 存储[的大小(T)];
};

注意,构造从()方法设计为带λ,而不是带构造器参数.这在用函数调用结果初化变量时,允许利用有保证的复制省略来原位构造对象.
如果带构造器参数,则最终会不必要地调用额外移动构造器.
现在可用此人工生命期结构为承诺.初挂起()返回的临时数据成员声明数据成员.

构__g状态{
    __g状态(&&x);
    整 x;
    __g_promise_t__承诺;
    人工生命期<::总是挂起>__1临;
    //待填充
};

标::总是挂起类型没有协待()符号,因此不必为此处调用的结果保留额外临时空间.
一旦调用初挂起()构造了该对象,需要调用三个方法来实现协待式:直接协(),挂起协()恢复协().

调用挂起协()时,要给它传递当前协程的句柄.现在可调用标::协柄<__g承诺型>::从承诺()并传递该承诺的引用.
此外,调用.挂起协(句柄)的结果有类型,因此与对布尔值和返回协柄的风格那样,不必考虑调用挂起协()后是恢复此协程还是另一个协程.

最后,因为标::总是挂起等待器上的所有方法调用都按无异声明,因此无需担心异常.如果它们可能抛,则需要添加额外代码,来确保在异常传播出斜坡函数前析构临时标::总是挂起对象.

一旦挂起协()成功返回或可开始执行协程体时,就可以抛异常时不再需要自动析构协程状态.
因此,可在拥有协程状态标::独针上调用释放()以避免从函数返回时析构它.
现在可实现初挂起式的第一部分,如下:

任务 g(整 x){::独针<__g状态>状态(新__g状态(静转<&&>(x)));
    推导()中值=状态->__承诺.取中();
    状态->__1临.构造从([&]()->推导(){
        中 状态->__承诺.初挂起();
    });(!状态->__1临.().直接协()){
        //...此处挂起协程
        状态->__1临.().挂起协(::协柄<__g承诺型>::从承诺(状态->__承诺));
        状态.释放();
        //落入下面的返回语句.
    }{
        //没有挂起`协程`.
        状态.释放();
        //...开始执行协程体
    }
    中__中值;
}

在协程体中而不是在斜坡函数中,调用恢复协()__1临的析构器.
现在求值初挂起点,(大部分)可工作,但是在该斜坡函数的代码中仍有几个待办.为了解决它,不忙,先看看挂起协程并稍后恢复协程的策略.

5:记录挂起点

挂起协程时,要确保在控制流中的挂起点恢复它.
还要跟踪在每个挂起点,有自动存储持续时间对象是否失活,以便知道如果析构而不是恢复协程,则要析构哪些对象.

可为协程中的每个挂起点赋值一个唯一编号,然后在协程状态整数数据成员中存储它.
然后,每当挂起协程时,都会在协程状态的挂起点编号成员中写入编号,恢复/析构时,检查此整数以查看在哪个挂起点挂起.
注意,这不是在协程状态下存储挂起点的唯一方法,但是所有3个主要编译器(微软,c语言,g编)都使用此方法.

另一个潜在方法是为每个挂起点使用单独的恢复/消灭函数指针.
因此,用整数数据成员存储挂起点索引并初化为零(0作为初始值)来扩展协程状态.

构__g状态{
    __g状态(&&x);
    整 x;
    __g_promise_t__承诺;
    整__挂起点=0;//<--添加挂起点索引
    人工生命期<::总是挂起>__1临;
    //待填充
};

第6步:实现coroutine_handle::resume()coroutine_handle::destroy()

调用协柄::恢复()恢复协程时,需要最终调用一些实现挂起协程体其余部分的函数.然后,调用体函数可查找挂起点索引并跳转到控制流中的相应点.

还要实现协柄::消灭()函数,以便它调用适当逻辑来析构当前挂起点处的域内对象,且要实现协柄::完成()来查询当前挂起点是否是最终挂起点.

协柄方法的接口不知道具体的协程状态类型,协柄<空>类型可指向任一协程实例.表明需要类型擦除协程状态类型.

为此,可存储该协程类型的恢复/消灭函数的函数指针,并让协柄::恢复/消灭()调用这些函数指针.
协柄类型还需要可使用协柄::地址()协柄::从地址()方法,来转换为空*或从空*转换.

此外,可从任一协程句柄恢复/析构协程,而不仅是传递给最新挂起协()调用的句柄.

因此要定义协柄类型,以便它只包含协程状态指针,且按协程状态的数据成员而不是在协柄中,存储恢复/消灭函数指针.

此外,因为需要协柄来指向任意协程状态对象,因此需要在所有协程状态类型中,函数指针数据成员的布局保持一致.
一个直接方法是让每个协程状态类型从包含这些数据成员的某个基类继承.
如,可按所有协程状态类型基类定义以下类型

构__协程状态{
    用__恢复函数=(__协程状态*);
    用__消灭函数=(__协程状态*);
    __恢复函数*__恢复;
    __消灭函数*__消灭;
};

然后协柄::恢复()方法可传递__协程状态对象指针简单调用__恢复().
类似,可为协柄::消灭()方法和__消灭函数指针这样.

协柄::完成()方法,选择以挂起无效__恢复函数指针为最终点.这很方便,因为最终挂起点不支持恢复(),只支持消灭().
如果有人在最终挂起点(有未定义行为)挂起的协程上调用恢复(),则最终会调用很快就会失败并指出他们错误的函数指针.
因此,可如下实现协柄<空>类型:

名间 标
{<型名 承诺=>
    类 协柄;<>
    类 协柄<>{:
        协柄()无异=默认;
        协柄(常 协柄&)无异=默认;
        协柄&符号=(常 协柄&)无异=默认;*地址(){
            中 静转<*>(状态);
        }
        静 协柄 从地址(*){
            协柄 h;
            h.状态=静转<__协程状态*>();
            中 h;
        }
        显 符号 极()无异{
            中 状态!=空针;
        }
        友 极 符号==(协柄 a,协柄 b)无异{
            中 a.状态==b.状态;
        }
        空 恢复(){
            状态->__恢复(状态);
        }
        空 消灭(){
            状态->__消灭(状态);
        }
        极 完成(){
            中 状态->__恢复==空针;
        }:
        __协程状态*状态=空针;
    };
}

第7步:实现coroutine_handle::promise()from_promise()

对更通用的协柄<承诺>特化,大多数实现都可重用协柄<空>实现.但是,还需要可访问从承诺()方法返回的协程状态承诺对象,并从承诺对象引用构建一个协柄.

但是,不能简单指向具体协程状态类型,因为协柄<承诺>类型必须可引用承诺类型是承诺协程状态.
要定义个新的从__协程状态继承并包含承诺对象的协程状态基类,以便可定义使用指定承诺类型,并从该基类继承的所有协程状态类型.

<型名 承诺>
构__带承诺协程状态:__协程状态{
    __带承诺协程状态()无异{}
    ~__带承诺协程状态(){}{
        承诺__承诺;
    };
};

为什么在此匿名联内声明__承诺成员?

原因是,为指定协程函数创建的继承类包含参数复制数据成员的定义.默认,在基类的数据成员之后初化继承类中的数据成员,因此按普通数据成员声明承诺对象,表明它是在参数复制数据成员前构造的.

但是,要在参数副本构造器之后调用承诺的构造器,可能需要把参数副本的引用传递给承诺构造器.

因此,在此基类中为承诺对象保留存储,以便它与协程状态的开头有一致的偏移,但让继承类负责在初化参数副本后适当位置调用构造器/析构器.
按联声明__承诺成员可满足要求.
更新__g状态类,以便现在从该新的基类继承.

构__g状态:__带承诺协程状态<__g承诺型>{
    __g状态(&&__x)
    :x(静转<&&>(__x)){
        //使用`原位新`在基类中初化`承诺`对象
        ::((*)::的地址(->__承诺))
            __g承诺型(构造承诺<__g承诺型>(x));
    }
    ~__g状态(){
        //还需要在`析构`参数对象前,手动调用`承诺`析构器.->__承诺.~__g承诺型();
    }
    整__挂起点=0;
    整 x;
    人工生命期<::总是挂起>__1临;
    //待填充
};

现在已定义了承诺基类,现在可实现标::协柄<承诺>类模板.

除了使用__带承诺协程状态<承诺>指针而不是__协程状态指针外,大多数实现应该与协柄<空>中的等效方法基本相同.
唯一新东西是添加了承诺()从承诺()函数.
1,承诺()方法很直接,它只返回协程状态的__承诺成员的引用.
2,从承诺()方法要求从承诺对象的地址计算协程状态地址.可从承诺对象的地址中减去__承诺成员的偏移来完成.
实现协柄<承诺>:

名间 标
{<型名 承诺>
    类 协柄{
        用 状态型=__带承诺协程状态<承诺>;:
        协柄()无异=默认;
        协柄(常 协柄&)无异=默认;
        协柄&符号=(常 协柄&)无异=默认;
        符号 协柄<>()常 无异{
            中 协柄<>::从地址(地址());
        }
        显 符号 极()常 无异{
            中 状态!=空针;
        }
        友 极 符号==(协柄 a,协柄 b)无异{
            中 a.状态==b.状态;
        }*地址(){
            中 静转<*>(静转<__协程状态*>(状态));
        }
        静 协柄 从地址(*){
            协柄 h;
            h.状态=静转<状态型*>(静转<__协程状态*>());
            中 h;
        }
        承诺&承诺(){
            中 状态->__承诺;
        }
        静 协柄 从承诺(承诺&承诺){
            协柄 h;
             //知道`__承诺`成员的地址,因此通过从该地址中减去`__承诺`字段的偏移来计算协程状态的地址.
            h.状态=重转<状态型*>(
                重转<正 符*>(::的地址(承诺))-
                的偏移(状态型,__承诺));
            中 h;
        }
        //根据`"协柄<空>"`实现来定义它们
        空 恢复(){
            静转<协柄<>>(*).恢复();
        }
        空 消灭(){
            静转<协柄<>>(*).消灭();
        }
        极 完成(){
            中 静转<协柄<>>(*).完成();
        }:
        状态型*状态;
    };
}

现在已定义了恢复协程机制,现在可返回到的"斜坡"函数,并更新它以初化添加到协程状态新函数指针数据成员.

8:协程体的开始

现在前向声明正确签名的恢复/消灭函数,并更新__g状态构造器以初化协程状态,以便恢复/析构器指针指向它们:

空__g恢复(__协程状态*s);
空__g消灭(__协程状态*s);
构__g状态:__带承诺协程状态<__g承诺型>{
    __g状态(&&__x)
    :x(静转<&&>(__x)){
        //初化`协柄`方法使用的函数指针.->__恢复=&__g恢复;->__消灭=&__g消灭;
        //用原位新`初化`基类中的`承诺`对象
        ::((*)::的地址(->__承诺))
            __g承诺型(构造承诺<__g承诺型>(x));
    }
    //其余部分省略
};
任务 g(整 x){::独针<__g状态>状态(新__g状态(静转<&&>(x)));
    推导()中值=状态->__承诺.取中();
    状态->__1临.构造从([&]()->推导(){
        中 状态->__承诺.初挂起();
    });(!状态->__1临.().直接协()){
        状态->__1临.().挂起协(::协柄<__g承诺型>::从承诺(状态->__承诺));
        状态.释放();
        //落入下面的返回语句.
    }{
        //未挂起`协程`.立即开始执行协程体.
        __g恢复(状态.释放());
    }
    中 中值;
}

完成了斜坡函数,现在可专注g()恢复/析构器.

从完成初挂起式的降级开始.

调用__g恢复(),且__挂起点索引为0时,需要在__1临上调用恢复协()然后调用__1临的析构器来恢复.

空__g恢复(__协程状态*s){
    //`"s"`指向`__g状态`.*状态=静转<__g状态*>(s);
    //生成跳转表,来根据`挂起点索引`的值跳转到代码中的正确位置.
    开关(状态->__挂起点){0:至 挂起点_0;
    默认:::不可达();
    }
挂起点_0:
    状态->__1临.().恢复协();
    状态->__1临.消灭();
    //`待办`:实现协程体的其余部分.`整 fx=协待 f(x);协中 fx*fx;`
}

调用__g消灭()__挂起点索引为0时,要先析构__1临,然后再析构并释放协程状态.

空__g消灭(__协程状态*s){*状态=静转<__g状态*>(s);
    开关(状态->__挂起点){0:至 挂起点_0;
    默认:::不可达();
    }
挂起点_0:
    状态->__1临.消灭();
    至 消灭状态;
    //待办:在此为其他`挂起点`添加额外逻辑.
消灭状态:
    删 状态;
}

9:降级co_await

接着,看看降级协待 f(x)式.
首先,要计算返回临时任务对象f(x).
因为直到语句尾的分号才会析构临时任务,且语句包含协待式,因此任务的生命期跨一个挂起点,因此必须在协程状态中存储任务.

在此临时任务上计算协待式时,要调用返回临时等待器对象的协待()符号方法.此对象的生命期也跨挂起点,因此必须以协程状态存储它.
添加必要成员到__g状态类型中:

构__g状态:__带承诺协程状态<__g承诺型>{
    __g状态(&&__x);
    ~__g状态();
    整__挂起点=0;
    整 x;
    人工生命期<::总是挂起>__1临;
    人工生命期<任务>__2临;
    人工生命期<任务::等待器>__3临;
};

然后可更新__g恢复()函数来初化这些临时函数,然后计算构成协待式其余部分的3个直接协,挂起协恢复协调用.
注意,任务::等待器::挂起协()方法返回一个协程句柄,因此需要生成恢复返回句柄的代码.
还要在调用挂起协()前更新挂起点索引(挂起对此点使用1索引),然后在跳转表中添加个额外的项以确保恢复到正确位置.

空__g恢复(__协程状态*s){
    //知道`"s"`指向`__g状态`.*状态=静转<__g状态*>(s);
    //生成跳转表,根据`挂起点`索引值跳转到代码中的正确位置.
    开关(状态->__挂起点){0:至 挂起点_0;1:至 挂起点_1;//<--添加新的跳转表项
    默认:::不可达();
    }
挂起点_0:
    状态->__1临.().恢复协();
    状态->__1临.消灭();
    //`整 fx=协待 f(x);`
    状态->__2临.构造从([&]{f(状态->x);
    });
    状态->__3临.构造从([&]{
        中 静转<任务&&>(状态->__2临.()).符号 协待();
    });(!状态->__3临.().直接协()){
        //标记挂起点
        状态->__挂起点=1;
        动 h=状态->__3临.().挂起协(::协柄<__g承诺型>::从承诺(状态->__承诺));
        //返回前`恢复`返回的协程句柄.
        h.恢复();;
    }
挂起点_1:
    整 fx=状态->__3临.().恢复协();
    状态->__3临.消灭();
    状态->__2临.消灭();
    //待办:实现`协中 fx*fx;`
}

注意,整 fx局部变量的生命期不跨挂起点,因此不需要以协程状态存储它.可在__g恢复函数中按普通局部变量存储它.
还要向__g消灭()函数添加,在此挂起点何时析构协程的必要项.

空__g消灭(__协程状态*s){*状态=静转<__g状态*>(s);
    开关(状态->__挂起点){0:至 挂起点_0;1:至 挂起点_1;//<--添加新的跳转表项
    默认:::不可达();
    }
挂起点_0:
    状态->__1临.消灭();
    至 消灭状态;
挂起点_1:
    状态->__3临.消灭();
    状态->__2临.消灭();
    至 消灭状态;
    //待办:在此为其他挂起点添加额外逻辑.
消灭状态:
    删 状态;
}

所以现在已完成了语句的实现:

整 fx=协待 f(x);

但是,未按无异标记f(x)函数,因此可能会触发异常.此外,等待器::恢复协()方法也没有按无异标记,因此也可能触发异常.

协程体抛异常时,编译器会生成代码来抓异常,然后调用承诺.对异常()让承诺处理异常.看看实现.

10:实现unhandled_exception()

规范说,协程行为类似:

{
    承诺-类型 承诺 承诺-构造器-实参;{
        协待 承诺.初挂起();
        函数-}(...){(!初始-等待-恢复-调用);
        承诺.对异常();
    }-挂起:
    协待 承诺.终挂起();
}

已在斜坡函数中单独处理了初始恢复协调用的分支,因此在此无需担心.
调整__g恢复()函数以在主体周围插入试/抓块.
注意,需要小心地在试块内,放置跳转到正确位置开关,因为禁止使用进入试块.

此外,要小心,在试/抓块之外从挂起协()返回的协程句柄上调用.恢复().如果在返回的协程上调用.恢复()触发异常,则当前协程不应抓该异常,而应从恢复此协程的恢复()调用中传播出去.
因此,在函数顶部声明的变量存储协程句柄,然后跳转到试/抓外的地方,并在那里调用.恢复().

空__g恢复(__协程状态*s){*状态=静转<__g状态*>(s);::协柄<>待恢复协程;{
        开关(状态->__挂起点){0:至 挂起点_0;1:至 挂起点_1;//<--添加新的跳转表项
        默认:::不可达();
        }
挂起点_0:
        状态->__1临.().恢复协();
        状态->__1临.消灭();
        //整 fx=协待 f(x);
        状态->__2临.构造从([&]{f(状态->x);
        });
        状态->__3临.构造从([&]{
            中 静转<任务&&>(状态->__2临.()).符号 协待();
        });(!状态->__3临.().直接协()){
            状态->__挂起点=1;
            待恢复协程=状态->__3临.().挂起协(::协柄<__g承诺型>::从承诺(状态->__承诺));
            至 恢复协程;
        }
挂起点_1:
        整 fx=状态->__3临.().恢复协();
        状态->__3临.消灭();
        状态->__2临.消灭();
        //待办:实现`协中 fx*fx;`
    }(...){
        状态->__承诺.对异常();
        至 终挂起;
    }
终挂起:
    //待办:实现`协待 承诺.终挂起();`
恢复协程:
    待恢复协程.恢复();;
}

但是,上面的代码中有个错误.如果带异常退出__3临.取().恢复协()调用,在抓到异常前无法调用__3临__2临的析构器.

注意,不能在此简单抓异常,调用析构器并重抛异常,因为如果这些析构器调用标::未处理异常(),因为会处理异常,这会改变这些析构器的行为.
但是,如果在展开异常时析构器调用此函数,则调用标:::未处理异常()应返回非零值.
可改为定义资取化助手类,来确保触发异常时在出域时调用析构器.

<型名 T>
构 析构器警卫{
    显 析构器警卫(人工生命期<T>&对象)无异
    :_(::的地址(对象))
    {}
    //不可移动
    析构器警卫(析构器警卫&&)=;
    析构器警卫&符号=(析构器警卫&&)=;
    ~析构器警卫()无异(::是可析构不抛值<T>){(针_!=空针){
            针_->消灭();
        }
    }
    空 取消()无异{针_=空针;}:
    人工生命期<T>*针_;
};
//不需要调用析构器的类型的部分特化.<型名 T>
    要求 标::是可平凡析构值<T>
构 析构器警卫<T>{
    显 析构器警卫(人工生命期<T>&)无异{}
    空 取消()无异{}
};
//推导类模板参数以简化使用<型名 T>
析构器警卫(人工生命期<T>&对象)->析构器警卫<T>;

使用此工具,现在可在抛异常时,用此类型来确保析构存储在协程状态中的变量.
还可用此类来调用现有变量的析构器,以便在它们自然出域时也调用它们的析构器.

空__g恢复(__协程状态*s){*状态=静转<__g状态*>(s);::协柄<>待恢复协程;{
        开关(状态->__挂起点){0:至 挂起点_0;1:至 挂起点_1;//<--添加新的跳转表项
        默认:::不可达();
        }
挂起点_0:
        {
            析构器警卫 临1析构器{状态->__1临};
            状态->__1临.().恢复协();
        }
        //整 fx=协待 f(x);
        {
            状态->__2临.构造从([&]{f(状态->x);
            });
            析构器警卫 临2析构器{状态->__2临};
            状态->__3临.构造从([&]{
                中 静转<任务&&>(状态->__2临.()).符号 协待();
            });
            析构器警卫 临3析构器{状态->__3临};(!状态->__3临.().直接协()){
                状态->__挂起点=1;
                待恢复协程=状态->__3临.().挂起协(::协柄<__g承诺型>::从承诺(状态->__承诺));
                //不退出域的挂起`协程`.所以取消析构器守卫.3析构器.取消();2析构器.取消();
                至 恢复协程;
            }
             //不要在此退出域.不能"跳转到"进入带`非平凡析构器`变量域的标签.因此,在此必须不调用`析构器`,退出`析构器`保护的域,然后在`"挂起点_1"`标签后重建它们.3析构器.取消();2析构器.取消();
        }
挂起点_1:
        整 fx=[&]()->推导(){
            析构器警卫 临2析构器{状态->__2临};
            析构器警卫 临3析构器{状态->__3临};
            中 状态->__3临.().恢复协();
        }();
        //待办:实现`协中 fx*fx;`
    }(...){
        状态->__承诺.对异常();
        至 终挂起;
    }
终挂起:
    //待办:实现`协待 承诺.终挂起();`
    //
恢复协程:
    待恢复协程.恢复();;
}

现在,协程体在有异常时正确析构局部变量,如果这些异常从协程体传播出去,会正确调用承诺.对异常().
注意,也要特殊处理承诺.对异常()方法自身带异常退出的情况(如,如果它重抛当前异常).
此时,协程要抓异常,在最终挂起点按挂起标记协程,然后重新触发异常.
如:__g恢复()函数的抓块如下:

{
  //...
}(...){{
        状态->__承诺.对异常();
    }(...){
        状态->__挂起点=2;
        状态->__恢复=空针;//按最终挂起点标记;
    }
}

还要在__g消灭函数的跳转表中添加个额外项:

开关(状态->__挂起点){0:至 挂起点_0;1:至 挂起点_1;2:至 消灭状态;//域中没有需要析构的变量,只需析构`协程状态`对象.
}

注意,此时,最终挂起点不一定与协待 承诺.终挂起()的挂起点相同.
这是因为承诺.终挂起()挂起点一般会有一些调用协柄::消灭()时,与协待式关联的需要析构的额外临时对象.

然而,如果承诺.对异常()带异常退出,则会析构这些临时对象,因此不需要由协柄::消灭()析构它.

11:实现co_return

下一步是实现协中 fx*fx;语句.
与前面步骤相比,这相对简单.
映射协中<式>语句为:

承诺.中值(<>);
至 止-挂起-;

因此,可简单地替换待办注解为:

状态->__承诺.中值(fx*fx);
至 终挂起;

容易.

12:实现final_suspend()

代码中的最后待办现在是实现协待 承诺.终挂起()语句.
终挂起()方法返回要在协程状态中存储,并在__g消灭中析构的临时任务::承诺类型::止等待器类型.

它没有自己的协待()符号,因此不必用额外临时对象来取调用结果.
任务::等待器类型一样,它也使用挂起协()协程句柄返回形式.所以在返回的句柄上要确保调用恢复().

如果未在最终挂起点挂起协程,则隐式析构协程状态.因此,如果执行到达协程尾,就需要删除状态对象.
此外,因为所有最终挂起逻辑都要求为无异,因此无需担心此处的子式会触发异常.
先添加数据成员到__g状态类型.

构__g状态:__带承诺协程状态<__g承诺型>{
    __g状态(&&__x);
    ~__g状态();
    整__挂起点=0;
    整 x;
    人工生命期<::总是挂起>__1临;
    人工生命期<任务>__2临;
    人工生命期<任务::等待器>__3临;
    人工生命期<任务::承诺类型::止等待器>__4临;//<---
};

然后可实现止-挂起式的主体,如下:

终挂起:
    //协待 承诺.终挂起
    {
        状态->__4临.构造从([&]()无异{
            中 状态->__承诺.终挂起();
        });
        析构器警卫 临4析构器{状态->__4临};(!状态->__4临.().直接协()){
            状态->__挂起点=2;
            状态->__恢复=空针;//按最终挂起点标记
            待恢复协程=状态->__4临.().挂起协(::协柄<__g承诺型>::从承诺(状态->__承诺));4析构器.取消();
            至 恢复协程;
        }
        状态->__4临.().恢复协();
    }
    //如果执行到达协程尾,则析构协程状态
    删 状态;;

现在还需要更新__g消灭函数来处理该新的挂起点.

空__g消灭(__协程状态*状态){*状态=静转<__g状态*>(s);
    开关(状态->__挂起点){0:至 挂起点_0;1:至 挂起点_1;2:至 挂起点_2;
    默认:::不可达();
    }
挂起点_0:
    状态->__1临.消灭();
    至 消灭状态;
挂起点_1:
    状态->__3临.消灭();
    状态->__2临.消灭();
    至 消灭状态;
挂起点_2:
    状态->__4临.消灭();
    至 消灭状态;
消灭状态:
    删 状态;
}

现在有个全功能的g()协程函数的降级.
大功告成!就是这样!

13:实现对称转移和无操协程(地址)

表明,上述__g恢复()函数的实现方式有问题.见前面
[式.等待]规范提示了应该如何处理挂起协协程句柄返回风格:
如果等待-挂起的类型为标::协程_-句柄,则计算等待-挂起.恢复().
注:这恢复等待挂起结果引用的协程.可这样连续恢复多个协程,最终控制流返回到当前协程调用者或恢复程序.
注解虽非规范,因此无约束,但强烈鼓励编译器如下实现:实现尾调用以恢复下个协程,而不是递归恢复下个协程.

这是因为如果在循环中相互恢复协程,则递归恢复下个协程很容易导致无限栈增长.

问题是在在__g恢复()函数的主体中的下个协程上调用.恢复(),然后返回,因此在下个协程挂起并返回后才会释放__g恢复()帧使用的栈空间.

编译器可按尾调用恢复下个协程.这样,编译器生成的代码首先弹出当前栈帧,保留返回地址,然后跳转到下个协程的恢复函数.

因为C++不能指定按尾调用调用函数,因此需要实际恢复函数返回,以便可释放其栈空间,然后让调用者恢复下个协程.

因为在挂起时,下个协程可能会无限地,需要恢复另一个协程,因此调用者需要在循环恢复协程.
此循环一般叫"蹦床循环",因为从一个协程返回到循环,然后从循环"反弹"回下个协程.

如果修改恢复函数签名以返回下个协程的协程状态的指针,而不是返回,则协柄::恢复()函数可立即调用__恢复()函数指针,让下个协程恢复它.

更改__协程状态__恢复函数的签名:

构__协程状态{
    用__恢复函数=__协程状态*(__协程状态*);
    用__消灭函数=(__协程状态*);
    __恢复函数*__恢复;
    __消灭函数*__消灭;
};

然后可如下编写协柄::恢复()函数:

空 标::协柄<>::恢复(){
    __协程状态*s=状态;{
        s=s->__恢复(s);
    }(/*一些条件*/);
}

然后问题变成了:“条件应该是什么”
因此引入标::无操协程()助手.

标::无操协程()是个返回特殊的有无操作恢复()消灭()方法的协程句柄的工厂函数.
如果挂起协程并从挂起协()方法返回无操作协程句柄,则表明没有更多要恢复的协程,且恢复此协程的协程句柄::恢复()的调用应返回给调用者.

因此,要实现标::无操协程()协柄::恢复()中的条件,以便条件返回,且当__协程状态指针指向无操协程状态时退出循环.

在此可定义一个按无操协程状态指定的__协程状态的静态实例.标::无操协程()函数可返回该对象协程句柄,可比较__协程状态指针与该对象地址,以查看指定协程句柄是否是无操协程.

首先,定义该特殊的无操协程状态对象:

构__协程状态{
    用__恢复函数=__协程状态*(__协程状态*);
    用__消灭函数=(__协程状态*);
    __恢复函数*__恢复;
    __消灭函数*__消灭;
    静__协程状态*__无操恢复(__协程状态*状态)无异{
        中 状态;
    }
    静 空__无操消灭(__协程状态*)无异{}
    静 常__coroutine_state__无操协程;
};
内联 常__coroutine_state__coroutine_state::__无操协程{
    &__协程状态::__无操恢复,
    &__协程状态::__无操消灭
};

然后可特化标::协柄<无操协程承诺>.

名间 标
{
    构 无操协程承诺{};
    用 无操协程句柄=协柄<无操协程承诺>;
    无操协程句柄 无操协程()无异;<>
    类 协柄<无操协程承诺>{:
        常式 协柄(常 协柄&)无异=默认;
        常式 协柄&符号=(常 协柄&)无异=默认;
        常式 显 符号 极()无异{中 真;}
        常式 友 极 符号==(协柄,协柄)无异{
            中 真;
        }
        符号 协柄<>()常 无异{
            中 协柄<>::从地址(地址());
        }
        无操协程承诺&承诺()常 无异{
            静 无操协程承诺 承诺;
            中 承诺;
        }
        常式 空 恢复()常 无异{}
        常式 空 消灭()常 无异{}
        常式 极 完成()常 无异{中 假;}
        常式 空*地址()常 无异{
            中 常转<__协程状态*>(&__协程状态::__无操协程);
        }:
        常式 协柄()无异=默认;
        友 无操协程句柄 无操协程()无异{{};
        }
    };
}

可更新协柄::恢复()以在返回无操协程状态时退出.

空 标::协柄<>::恢复(){
    __协程状态*s=状态;{
        s=s->__恢复(s);
    }(s!=&__协程状态::__无操协程);
}

最后,可更新__g恢复()函数,现在返回__协程状态*.
这仅涉及更新签名及:

//用
动 h=...;
中 静转<__协程状态*>(h.地址());
//替换
待恢复协程=...;
至 恢复协程;

然后在函数的最后(在删除 状态;语句之后)添加

中 静转<__协程状态*>(::无操协程().地址());

最后

协程状态类型的__g状态比它需要的更大.
4个临时值的数据成员各自保留了相应存储.但是,某些临时值生命期不会重叠,因此从理论上讲,可在对象生命期结束后为下个对象重用对象存储来节省协程状态空间.

为了利用它,可在适当时在匿名联中定义数据成员.
查看拥有的临时变量的生命期:
1,__1临仅在协待 承诺.初挂起();语句中
2,__2临仅在整 fx=协待 f(x)
3,__3临仅在嵌套在__2临的生命期内的整 fx=协待 f(x)
4,__4临仅在协待 承诺.终挂起()中.
因为__2临__3临的生命期重叠,必须把它们放在一个结构中,因为它们需要同时存在.
但是,__1临__4临成员没有重叠生命期,因此可把它们放在一个匿名联中.
因此,可更改数据成员为:

构__g状态:__带承诺协程状态<__g承诺型>{
    __g状态(&&x);
    ~__g状态();
    整__挂起点=0;
    整 x;
    构__1域{
        人工生命期<任务>__2临;
        人工生命期<任务::等待器>__3临;
    };{
        人工生命期<::总是挂起>__1临;
        __1域__s1;
        人工生命期<任务::承诺类型::止等待器>__4临;
    };
};

然后,因为__2临__3临变量,现在嵌套在__s1对象中,要更新它们引用,如状态->__s1.2临.但除此外,其余代码不变.
这应该可节省额外的16个字节的协程状态大小,因为不再需要额外存储__1临__4临数据成员,否则尽管是空类型,也会被填充到指针大小.

最后绑定在一起
好的,所以为协程函数生成的最终代码:

任务 g(整 x){
    整 fx=协待 f(x);
    协中 fx*fx;
}

如下:

//协程承诺类型
用__g承诺型=::协征<任务,>::承诺类型;
__协程状态*__g恢复(__协程状态*s);
空__g消灭(__协程状态*s);
//协程状态定义
构__g状态:__带承诺协程状态<__g承诺型>{
    __g状态(&&x)
    :x(静转<&&>(x)){
        //初化`协柄`方法使用的函数指针.->__恢复=&__g恢复;->__消灭=&__g消灭;
         //在初化参数副本后,使用`原位新`初化基类中的`承诺`对象.
        ::((*)::的地址(->__承诺))
            __g承诺型(构造承诺<__g承诺型>(->x));
    }
    ~__g状态(){->__承诺.~__g承诺型();
    }
    整__挂起点=0;
    //参数副本
    整 x;
    //局部变量/临时变量
    构__1域{
        人工生命期<任务>__2临;
        人工生命期<任务::等待器>__3临;
    };{
        人工生命期<::总是挂起>__1临;
        __1域__s1;
        人工生命期<任务::承诺类型::止等待器>__4临;
    };
};
//"斜坡"函数
任务 g(整 x){::独针<__g状态>状态(新__g状态(静转<&&>(x)));
    推导()中值=状态->__承诺.取中();
    状态->__1临.构造从([&]()->推导(){
        中 状态->__承诺.初挂起();
    });(!状态->__1临.().直接协()){
        状态->__1临.().挂起协(::协柄<__g承诺型>::从承诺(状态->__承诺));
        状态.释放();
        //落入下面的返回语句.
    }{
        //没有挂起`协程`.立即开始执行协程体.
        __g恢复(状态.释放());
    }
    中 中值;
}
//"恢复"函数
__协程状态*__g恢复(__协程状态*s){*状态=静转<__g状态*>(s);{
        开关(状态->__挂起点){0:至 挂起点_0;1:至 挂起点_1;//<--添加新的跳转表项
        默认:::不可达();
        }
挂起点_0:
        {
            析构器警卫 临1析构器{状态->__1临};
            状态->__1临.().恢复协();
        }
        //整 fx=协待 f(x);
        {
            状态->__s1.__2临.构造从([&]{f(状态->x);
            });
            析构器警卫 临2析构器{状态->__s1.__2临};
            状态->__s1.__3临.构造从([&]{
                中 静转<任务&&>(状态->__s1.__2临.()).符号 协待();
            });
            析构器警卫 临3析构器{状态->__s1.__3临};(!状态->__s1.__3临.().直接协()){
                状态->__挂起点=1;
                动 h=状态->__s1.__3临.().挂起协(::协柄<__g承诺型>::从承诺(状态->__承诺));
                //不退出域挂起协程.所以取消析构器守卫.3析构器.取消();2析构器.取消();
                中 静转<__协程状态*>(h.地址());
            }
             //不要在此退出域.不能"跳转到"进入带`非平凡析构器`变量域的标签.因此,在此必须不调用`析构器`,退出`析构器`保护的域,然后在`"挂起点_1"`标签后重建它们.3析构器.取消();2析构器.取消();
        }
挂起点_1:
        整 fx=[&]()->推导(){
            析构器警卫 临2析构器{状态->__s1.__2临};
            析构器警卫 临3析构器{状态->__s1.__3临};
            中 状态->__s1.__3临.().恢复协();
        }();
        //`协中 fx*fx;`
        状态->__承诺.中值(fx*fx);
        至 终挂起;
    }(...){
        状态->__承诺.对异常();
        至 终挂起;
    }
终挂起:
    //`协待 承诺.终挂起`
    {
        状态->__4临.构造从([&]()无异{
            中 状态->__承诺.终挂起();
        });
        析构器警卫 临4析构器{状态->__4临};(!状态->__4临.().直接协()){
            状态->__挂起点=2;
            状态->__恢复=空针;//按最终挂起点标记
            动 h=状态->__4临.().挂起协(::协柄<__g承诺型>::从承诺(状态->__承诺));4析构器.取消();
            中 静转<__协程状态*>(h.地址());
        }
        状态->__4临.().恢复协();
    }
    //如果执行到协程尾,则析构协程状态
    删 状态;
    中 静转<__协程状态*>(::无操协程().地址());
}
//"析构"函数
空__g消灭(__协程状态*s){*状态=静转<__g状态*>(s);
    开关(状态->__挂起点){0:至 挂起点_0;1:至 挂起点_1;2:至 挂起点_2;
    默认:::不可达();
    }
挂起点_0:
    状态->__1临.消灭();
    至 消灭状态;
挂起点_1:
    状态->__s1.__3临.消灭();
    状态->__s1.__2临.消灭();
    至 消灭状态;
挂起点_2:
    状态->__4临.消灭();
    至 消灭状态;
消灭状态:
    删 状态;
}

你可能感兴趣的:(c++,cpp,c++)