...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 requires 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; std::exception_ptr except; // create handle to main execution context auto main_ctx( boost::context::execution_context::current() ); // execute parser in new execution context boost::context::execution_context parser_ctx( std::allocator_arg, boost::context::fixedsize_stack(4096), [&main_ctx,&is,&done,&except](void*){ // create parser with callback function Parser p( is, [&main_ctx,&c](char ch){ // resume main execution context main_ctx( & ch); }); try { // start recursive parsing p.run(); } catch ( ... ) { // store other exceptions in exception-pointer except = std::current_exception(); } // set termination flag done=true; // resume main execution context main_ctx(); }); // user-code pulls parsed data from parser // invert control flow void * vp = parser_ctx(); if ( except) { std::rethrow_exception( except); } while( ! done) { printf("Parsed: %c\n",* static_cast< char* >( vp) ); parser_ctx(); if ( except) { std::rethrow_exception( except); } } std::cout << "main: done" << std::endl; } 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 data (character) is transferred between the two execution_context.
If the code executed by execution_context emits an exception, the application is terminated. std::exception_ptr can be used to transfer exceptions between different execution contexts.
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-allocator 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( std::allocator_arg, preallocated( sp, size, sctx), salloc, entry_func) { } ... };
If the function executed inside a execution_context emits ans exception, the application is terminated by calling ['std::terminate(). std::exception_ptr can be used to transfer exceptions between different execution contexts.
The void pointer argument passed to execution_context::operator(), in one context, is passed as the last argument of the context-function if the context is started for the first time. In all following invocations of execution_context::operator() the void pointer passed to execution_context::operator(), in one context, is returned by execution_context::operator() in the other context.
class X { private: std::exception_ptr excptr_; boost::context::execution_context caller_; boost::context::execution_context callee_; public: X() : excptr_(), caller_( boost::context::execution_context::current() ), callee_( [=] (void * vp) { try { int i = * static_cast< int * >( vp); std::string str = boost::lexical_cast<std::string>(i); caller_( & str); } catch (...) { excptr_=std::current_exception(); } }) {} std::string operator()( int i) { void * ret = callee_( & i); if(excptr_){ std::rethrow_exception(excptr_); } return * static_cast< std::string * >( ret); } }; 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 Fn, typename ... Args > execution_context( Fn && fn, Args && ... args); template< typename StackAlloc, typename Fn, typename ... Args > execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Args && ... args); template< typename StackAlloc, typename Fn, typename ... Args > execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args); execution_context( execution_context const& other) noexcept; execution_context( execution_context && other) noexcept; execution_context & operator=( execution_context const& other) noexcept; execution_context & operator=( execution_context && other) noexcept; explicit operator bool() const noexcept; bool operator!() const noexcept; void * operator()( void * vp = nullptr) noexcept; bool operator==( execution_context const& other) const noexcept; bool operator!=( execution_context const& other) const noexcept; bool operator<( execution_context const& other) const noexcept; bool operator>( execution_context const& other) const noexcept; bool operator<=( execution_context const& other) const noexcept; bool operator>=( execution_context const& other) const noexcept; template< typename charT, class traitsT > friend std::basic_ostream< charT, traitsT > & operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other); };
static execution_context
current()
Returns an instance of excution_context pointing to the active execution context.
Nothing.
template< typname Fn, typename ... Args > execution_context( Fn &&
fn, Args &&
... args)
Creates a new execution context and prepares the context to execute
fn
. fixedsize_stack
is used as default stack allocator (stack size == fixedsize_stack::traits::default_size().
template< typename StackAlloc, typname Fn, typename
... Args
> execution_context( std::allocator_arg_t,
StackAlloc salloc, Fn &&
fn, Args &&
... args)
Creates a new execution context and prepares the context to execute
fn
.
template< typename StackAlloc, typname Fn, typename
... Args
> execution_context( std::allocator_arg_t,
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.
execution_context(
execution_context const& other)
Copies other
, e.g. underlying
capture record is shared with *this
.
Nothing.
execution_context(
execution_context &&
other)
Moves underlying capture record to *this
.
Nothing.
execution_context &
operator=(
execution_context const& other)
Copies the state of other
to *this
,
state (capture record) is shared.
Nothing.
execution_context &
operator=(
execution_context &&
other)
Moves the state of other
to *this
using move semantics.
Nothing.
explicit operator
bool() const noexcept
true
if *this
points to a capture record.
Nothing.
bool operator!() const noexcept
true
if *this
does not point to a capture record.
Nothing.
void * operator()( void *
vp) noexcept
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 void pointer argument, vp
,
is passed to the current context to be returned by the most recent call
to execution_context::operator()
in the same thread. [[Note:
The behaviour is undefined if operator()()
is called while execution_context::current()
returns *this
(e.g. resuming an already running
context). If the top-level context function returns, std::exit()
is called.
The void pointer argument passed to the most recent call to execution_context::operator()
,
if any.
Nothing.
operator==
bool operator==( execution_context const& other) const noexcept;
true
if *this
and other
represent the same execution context, false
otherwise.
Nothing.
operator!=
bool operator!=( execution_context const& other) const noexcept;
[`! (other == * this)
[[Throws:] [Nothing.]] ]
operator<
bool operator<( execution_context const& other) const noexcept;
true
if *this != other
is true and the implementation-defined
total order of execution_context
values places *this
before other
, false otherwise.
Nothing.
operator>
bool operator>( execution_context const& other) const noexcept;
other <
* this
Nothing.
operator<=
bool operator<=( execution_context const& other) const noexcept;
! (other <
* this)
Nothing.
operator>=
bool operator>=( execution_context const& other) const noexcept;
! (*
this <
other)
Nothing.
operator<<
template< typename charT, class traitsT > std::basic_ostream< charT, traitsT > & operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other);
Writes the representation of other
to stream os
.
os
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)
Creates an object of preallocated.