1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
|
NOUSERMACRO(operator)
When defining coroutines the tthi(coroutine) header file must be included.
A function is a coroutine once it uses the keywords ti(co_yield),
ti(co_await), or ti(co_return). A coroutines cannot use the tt(return)
keyword, cannot define variadic parameters, and its return type must be an
existing type, which defines the hi(handler (coroutine)) em(coroutine's
handler).
Although coroutines appear to return objects (as suggested by the tt(Fibo)
return type of the tt(Fibo fiboCoro()) coroutine defined in the
previous section), in fact they do not. Instead coroutines return
so-called `handlers'. Such a handler is tt(fibo), defined and used in the
previous section's tt(main) function:
verbinsert(-as4 demo/fibocoro/main.2)
The class tt(Fibo) itself defines the characteristics allowing the compiler to
generate code storing the coroutine's arguments, its local variables, the
location of the next instruction to execute when the coroutine returns or is
suspended, and its so-called emi(promise_type) object on the heap. This only
happens once, so when the coroutine is activated (as in tt(sum +=
fibo.next())) the steps which are normally taken when a function is called are
avoided, and instead the coroutine is immediately executed, using its already
available local variables and arguments.
hi(future (coroutine))hi(promise_type (coroutine))hi(cooperating routine)
Coroutine's handler classes are sometimes called tt(Future), and their
nested state classes em(must) be known as the handler's tt(promise_type). The
names em(future) and em(promise_type) are completely unrelated to the
tt(std::future) (cf. section ref(FUTURE)) and tt(std::promise) (cd. section
ref(PROMISE)) types which are used in the context of multi threading. In fact,
coroutines themselves are unrelated to multi threading, but are known as
em(cooperating routines). Because the coroutines' handler and state classes
are unrelated to the tt(future) and tt(promise) classes used in the context of
multi threading in this chapter the terms em(Handler) and tt(State) are
generally used.
It's one thing to define a coroutine, but when using a coroutine its
handler-class (the tt(Fibo) class in the current example) must also be
defined. In addition, hi(promise_type) such a handler-class em(must) define a
nested class whose name em(must) be publicly available as the handler's
tt(promise_type). The name tt(promise_type) doesn't very well cover its
purpose, and using a more descriptive class name might be preferred. In that
case a simple using declaration in the handler class's public section can be
used, as shown in the following basic design of the tt(Fibo) handler-class:
verbinsert(-as4 demo/fibocoro/fibo/fibo.2)
The coroutine's handler class has the following characteristics:
itemization(
it() it has a nested class (here: tt(State)) keeping track of the
couroutine's state;
it() it commonly defines a private data member of the type
ti(std::coroutine_handle<State>), e.g.,
tt(std::coroutine_handle<State> d_handle), whose members are covered
below;
it() Unless the handling class's nested class is called tt(promise_type) a
using declaraction must be specified to make the nested class name
also known as tt(promise_type);
it() Other members are optional, although usually there is at least a
member returning a value that's available in the coroutine's state,
like the member tt(next) which was used in the example's tt(main)
function:
verb(
sum += fibo.next();
)
The tt(next) member's current implementation resumes the coroutine.
This doesn't mean that when tt(next) is called for the first time,
that it's the very first activation of the coroutine: the call tt(auto
fibo = fiboCoro()) comes first, and constructing and returning the
coroutine's handler (thereby suspending the coroutine at its very
first statement) is done automatically. At that point the caller
receives the coroutine's handler object, and constructing and
returning the handler object isn't visible in the coroutine's code
(more about that in the next section). Once suspended at tt(co_yield)
the value that's available in the coroutine's tt(State) is returned
and made available to the coroutine's caller:
verbinsert(-s4 //next demo/fibocoro/fibo/next.cc)
)
The following members can be called via the Handler's tt(d_handle) data
member:
itemization(
itt(void *address()) hi(address) returning the address of the handler's
tt(State) object;
itt(void destroy()), hi(destroy) returning the tt(State) object's memory
to the operating system. It ends the tt(State) object's
existence. Usually the handler class's destructor calls
tt(d_handle.destroy()).
itt(bool done(),) hi(done) returning tt(true) when the coroutine has
returned, and tt(false) if it's currently suspended;
itt(coroutine_handle from_address(void *address)) hi(from_address) returns
a tt(coroutine_handle) corresponding to the address of a handler's
tt(State) object, which address is passed to the function as its
argument. A tt(nullptr) can also be passed to tt(from_address);
itt(explicit operator bool(),) hi(operator bool [coroutine]) returning
tt(true) if tt(d_handle) is not a null-pointe. It's commonly used in
the handler's destructor's tt(if (d_handle)) phrase. It returns
tt(false) after assigning 0 (or tt(nullptr)) to tt(d_handle). This
operator and tt(static_cast<bool>(d_handle.address())) act identically
(note that tt(d_handle.address()) is still valid after assigning 0 to
tt(d_handle), in which case it returns 0);
itt(State &promise(),) hi(promise [coroutine]) returning a reference to
the tt(Handler's State) class.
itt(void resume()) hi(resume) hi(operator() [coroutine])
(or tt(void operator()())) resumes the execution of a suspended
coroutine. Resuming a coroutine is only defined if the coroutine is
actually suspended.
)
The tt(Handler's State) class keeps track of the coroutine's state. Its basic
elements are covered in the next section.
|