...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Important | |
---|---|
execution_context is supported only by C++14. |
Class execution_context encapsulates fcontext_t
and related functions ( jump_fcontext() and make_fcontext())
as well as stack management. execution_context permits
access to the current, active context via execution_context::current()
.
/* * grammar: * P ---> E '\0' * E ---> T {('+'|'-') T} * T ---> S {('*'|'/') S} * S ---> digit | '(' E ')' */ class Parser{ // implementation omitted; see examples directory }; int main() { std::istringstream is("1+1"); bool done=false; char c; // create handle to main execution context boost::context::execution_context main_ctx( boost::context::execution_context::current() ); // executes parser in new execution context boost::context::execution_context parser_ctx( boost::context::fixedsize_stack(), [&main_ctx,&is,&c,&done](){ // create parser with callback function Parser p(is, [&main_ctx,&c](char ch){ c=ch; // resume main execution context main_ctx.resume(); }); // start recursive parsing p.run(); done=true; // return to main execution context main_ctx.resume(); }); // user-code pulls parsed data from parser // inverted control flow parser_ctx.resume(); do { printf("Parsed: %c\n",c); parser_ctx.resume(); } while( ! done); } output: Parsed: 1 Parsed: + Parsed: 1
In this example a recursive descent parser uses a callback to emit a newly passed symbol. Using execution_context the control flow can be inverted, e.g. the user-code pulls parsed symbols from the parser - instead to get pushed from the parser (via callback).
The interface of execution_context does not transfer data. This is not required because usually sharing data's address (pointer/reference) sufficient.
If the code executed by execution_context emits an exception,
std::terminate()
will be called.
Important | |
---|---|
Do not jump from inside a catch block and than re-throw exceptions in another execution context. |
Sometimes it is necessary to unwind the stack of an unfinished context to destroy local stack variables so they can release allocated resources (RAII pattern). The user is responsible for this task.
Allocating control structures on top of the stack requires to allocated the stack_context and create the control structure with placement new before execution_context is created.
Note | |
---|---|
The user is responsible for destructing the control structure at the top of the stack. |
// stack-alloctor used for (de-)allocating stack fixedsize_stack salloc( 4048); // allocate stack space stack_context sctx( salloc.allocate() ); // reserve space for control structure on top of the stack void * sp = static_cast< char * >( sctx.sp) - sizeof( my_control_structure); std::size_t size = sctx.size - sizeof( my_control_structure); // placement new creates control structure on reserved space my_control_structure * cs = new ( sp) my_control_structure( sp, size, sctx, salloc); ... // destructing the control structure cs->~my_control_structure(); ... struct my_control_structure { // execution context execution_context ectx; template< typename StackAllocator > my_control_structure( void * sp, std::size_t size, stack_context sctx, StackAllocator salloc) : // create execution context ectx( preallocated( sp, size, sctx), salloc, entry_func) { } ... };
If the function executed inside a execution_context emitts
ans exception, std::terminate()
is called - std::exception_ptr
can used to store exceptions
thrown inside the other context.
Input and output parameters are transfered via a lambda capture list and references/pointers.
class X { private: int * inp_; std::string outp_; std::exception_ptr excptr_; boost::context::execution_context caller_; boost::context::execution_context callee_; public: X() : inp_( nullptr), outp_(), excptr_(), caller_( boost::context::execution_context::current() ), callee_( boost::context::fixedsize_stack(), [=] () { try { int i = * inp_; outp_ = boost::lexical_cast< std::string >( i); caller_.resume(); } catch (...) { excptr_ = std::current_exception(); } }) {} std::string operator()( int i) { inp_ = & i; callee_.resume(); if ( excptr_) { std::rethrow_exception( excptr_); } return outp_; } }; int main() { X x; std::cout << x( 7) << std::endl; std::cout << "done" << std::endl; }
execution_context
class execution_context { public: static execution_context current() noexcept; template< typename StackAlloc, typename Fn > execution_context( StackAlloc salloc, Fn && fn); template< typename StackAlloc, typename Fn, typename ... Args > execution_context( StackAlloc salloc, Fn && fn, Args && ... args); template< typename StackAlloc, typename Fn > execution_context( preallocated palloc, StackAlloc salloc, Fn && fn); template< typename StackAlloc, typename Fn, typename ... Args > execution_context( preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args); void resume() noexcept; explicit operator bool() const noexcept; bool operator!() const noexcept; };
static execution_context
current()
Returns an instance of excution_context pointing to the active execution context.
Nothing.
template< typename StackAlloc, typname Fn > execution_context(
StackAlloc salloc, Fn &&
fn)
Creates a new execution context and prepares the context to execute
fn
.
template< typename StackAlloc, typname Fn, typename
... Args
> execution_context( StackAlloc salloc, Fn && fn, Args
&& ...
args)
Creates a new execution context and prepares the context to execute
fn
.
template< typename StackAlloc, typname Fn > execution_context(
preallocated palloc, StackAlloc salloc, Fn && fn)
Creates a new execution context and prepares the context to execute
fn
. Used to store control
structures on top of the stack.
template< typename StackAlloc, typname Fn, typename
... Args
> execution_context( preallocated
palloc,
StackAlloc salloc, Fn &&
fn, Args &&
... args)
Creates a new execution context and prepares the context to execute
fn
. Used to store control
structures on top of the stack.
void resume()
Stores internally the current context data (stack pointer, instruction
pointer, and CPU registers) to the current active context and restores
the context data from *this
, which implies jumping to *this
's
execution context.
The behaviour is undefined if resume()
is called while execution_context::current()
returns *this
(e.g. resuming an alredy running
cotnext).
Nothing.
explicit operator
bool() const
If *this
refers to an invalid context or the context-function has returned (completed),
the function returns false
.
Otherwise true
.
Nothing.
bool operator!() const
If *this
refers to an invalid context or the context-function has returned (completed),
the function returns true
.
Otherwise false
.
Nothing.
preallocated
struct preallocated { void * sp; std::size_t size; stack_context sctx; preallocated( void * sp, std:size_t size, stack_allocator sctx) noexcept; };
preallocated( void * sp, std:size_t size, stack_allocator
sctx)
Crreates an object of preallocated.