Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Class captured_context

Class captured_context encapsulates context switching and manages the associated context' stack (allocation/deallocation).

captured_context allocates the context stack (using its StackAllocator argument) and creates a control structure on top of it. This structure is responsible for managing context' stack. The address of the control structure is stored in the first frame of context' stack (e.g. it can not directly accessed from within captured_context). In contrast to execution_context the ownership of the control structure is not shared (no member variable to control structure in captured_context). captured_context keeps internally a state that is moved by a call of captured_context::operator() (*this will be invalidated), e.g. after a calling captured_context::operator(), *this can not be used for an additional context switch.

captured_context is only move-constructible and move-assignable.

The moved state is assigned to a new instance of captured_context. This object becomes the first argument of the context-function, if the context was resumed the first time or the object becomes the first element in a tuple returned by captured_context::operator() that has been called in the resumed context. In contrast to execution_context, the context switch is faster because no global pointer etc. is involved (that's the reason why segmented stacks are not supported by captured_context).

If an instance with valid state goes out of scope, the stack is traversed in order to access the control structure (address stored at the first stack frame) and to deallocate context' stack via the StackAllocator. The stack walking makes the destruction of captured_context very slow compared to execution_context.

captured_context expects a function/functor with signature captured_context(captured_context ctx, void* vp). The parameter ctx represents the context from which this context was resumed (e.g. that has called captured_context::operator() on *this) and vp is the data passed to captured_context::operator(). The return value is the captured_context that has to be resumed, while this context terminates.

[Important] Important

Segmented stacks are not supported by captured_context.

usage of captured_context

int n=35;
ctx::captured_context source(
    [n](ctx::captured_context sink,void*)mutable{
        int a=0;
        int b=1;
        while(n-->0){
            auto result=sink(&a);
            sink=std::move(std::get<0>(result));
            auto next=a+b;
            a=b;
            b=next;
        }
        return sink;
    });
for(int i=0;i<10;++i){
    auto result=source();
    source=std::move(std::get<0>(result));
    std::cout<<*(int*)std::get<1>(result)<<" ";
}

output:
    0 1 1 2 3 5 8 13 21 34

This simple example demonstrates the basic usage of captured_context. The context sink represents the main-context (function main() running). sink is generated by the framework (first element of lambda's parameter list). Because the state is invalidated (== changed) by each call of captured_context::operator(), sink has to be assigned the new state of the captured_context returned by captured_context::operator().

The lambda that calculates the Fibonacci numbers is executed inside the context represented by source. Calculated Fibonacci numbers are transferred between the two context' via expression sink(&a) (and returned by source()).

The locale variables a, b and next remain their values during each context switch (yield(a)). This is possible due ctx has its own stack and the stack is exchanged by each context switch.

inverting the control flow

/*
 * grammar:
 *   P ---> E '\0'
 *   E ---> T {('+'|'-') T}
 *   T ---> S {('*'|'/') S}
 *   S ---> digit | '(' E ')'
 */
class Parser{
    // implementation omitted; see examples directory
};

std::istringstream is("1+1");
bool done=false;
std::exception_ptr except;

// execute parser in new execution context
boost::context::captured_context source(
        [&is,&done,&except](ctx::captured_context sink,void*){
        // create parser with callback function
        Parser p( is,
                  [&sink](char ch){
                        // resume main execution context
                        auto result = sink(&ch);
                        sink = std::move(std::get<0>(result));
                });
            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
            return sink;
        });

// user-code pulls parsed data from parser
// invert control flow
auto result = source();
source = std::move(std::get<0>(result));
void * vp = std::get<1>(result);
if ( except) {
    std::rethrow_exception(except);
}
while( ! done) {
    printf("Parsed: %c\n",* static_cast<char*>(vp));
    std::tie(source,vp) = source();
    if (except) {
        std::rethrow_exception(except);
    }
}

output:
    Parsed: 1
    Parsed: +
    Parsed: 1

In this example a recursive descent parser uses a callback to emit a newly passed symbol. Using captured_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 captured_context.

If the code executed by captured_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 stack

Allocating control structures on top of the stack requires to allocated the stack_context and create the control structure with placement new before captured_context is created.

[Note] 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  {
    // captured context
    captured_context cctx;

    template< typename StackAllocator >
    my_control_structure( void * sp, std::size_t size, stack_context sctx, StackAllocator salloc) :
        // create captured context
        cctx( std::allocator_arg, preallocated( sp, size, sctx), salloc, entry_func) {
    }
    ...
};

exception handling

If the function executed inside a captured_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.

parameter passing

The void pointer argument passed to captured_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 captured_context::operator() the void pointer passed to captured_context::operator(), in one context, is returned by captured_context::operator() in the other context.

class X {
private:
    std::exception_ptr excptr_;
    boost::context::captured_context ctx_;

public:
    X() :
        excptr_(),
        ctx_([=](ctx::captured_context ctx, void* vp)mutable{
                    try {
                        for (;;) {
                            int i = * static_cast<int*>(vp);
                            std::string str = boost::lexical_cast<std::string>(i);
                            auto result = ctx(& str);
                            ctx = std::move(std::get<0>(result) );
                            vp = std::get<1>(result);
                        }
                    } catch (ctx::detail::forced_unwind const&) {
                        throw;
                    } catch (...) {
                        excptr_=std::current_exception();
                    }
                    return ctx;
             })
    {}

    std::string operator()(int i) {
        auto result = ctx_(&i);
        ctx_ = std::move(std::get<0>(result));
        void * ret = std::get<1>(result);
        if(excptr_){
            std::rethrow_exception(excptr_);
        }
        return * static_cast<std::string*>(ret);
    }
};

X x;
std::cout << x( 7) << std::endl;

output:
7

Class captured_context

class captured_context {
public:
    template< typename Fn, typename ... Args >
    captured_context( Fn && fn, Args && ... args);

    template< typename StackAlloc, typename Fn, typename ... Args >
    captured_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Args && ... args);

    template< typename StackAlloc, typename Fn, typename ... Args >
    captured_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args);

    ~captured_context();

    captured_context( captured_context && other) noexcept;
    captured_context & operator=( captured_context && other) noexcept;

    captured_context( captured_context const& other) noexcept = delete;
    captured_context & operator=( captured_context const& other) noexcept = delete;

    explicit operator bool() const noexcept;
    bool operator!() const noexcept;

    std::tuple< captured_context, void * > operator()( void * data = nullptr);

    template< typename Fn, typename ... Args >
    std::tuple< captured_context, void * > operator()( exec_ontop_arg_t, Fn && fn, Args && ... args);

    template< typename Fn, typename ... Args >
    std::tuple< captured_context, void * > operator()( void * data, exec_ontop_arg_t, Fn && fn, Args && ... args);

    bool operator==( captured_context const& other) const noexcept;

    bool operator!=( captured_context const& other) const noexcept;

    bool operator<( captured_context const& other) const noexcept;

    bool operator>( captured_context const& other) const noexcept;

    bool operator<=( captured_context const& other) const noexcept;

    bool operator>=( captured_context const& other) const noexcept;

    template< typename charT, class traitsT >
    friend std::basic_ostream< charT, traitsT > &
    operator<<( std::basic_ostream< charT, traitsT > & os, captured_context const& other);
};

Constructor

template< typename Fn, typename ... Args >
captured_context( Fn && fn, Args && ... args);

template< typename StackAlloc, typename Fn, typename ... Args >
captured_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Args && ... args);

template< typename StackAlloc, typename Fn, typename ... Args >
captured_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args);

Effects:

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()). The constructor with argument type preallocated, is used to create a user defined data (for instance additional control structures) on top of the stack.

Move constructor

captured_context( captured_context && other) noexcept;

Effects:

Moves underlying capture record to *this.

Throws:

Nothing.

Move assignment operator

captured_context & operator=( captured_context && other) noexcept;

Effects:

Moves the state of other to *this using move semantics.

Throws:

Nothing.

Member function operator bool()

explicit operator bool() const noexcept;

Returns:

true if *this points to a capture record.

Throws:

Nothing.

Member function operator!()

bool operator!() const noexcept;

Returns:

true if *this does not point to a capture record.

Throws:

Nothing.

Member function operator()()

std::tuple< captured_context, void * > operator()( void * data = nullptr);

Effects:

Stores internally the current context data (stack pointer, instruction pointer, and CPU registers) of the current active context and restores the context data from *this, which implies jumping to *this's context. The void pointer argument, vp, is passed to the current context to be returned by the most recent call to captured_context::operator() in the same thread. fn is executed with arguments args on top of the stack of this.

Note:

The behaviour is undefined if operator()() is called while captured_context::current() returns *this (e.g. resuming an already running context). If the top-level context function returns, std::exit() is called.

Returns:

The tuple of void pointer argument passed to the most recent call to captured_context::operator(), if any and a captured_context representing the context that has been suspended .

Member function operator()()

template< typename Fn, typename ... Args >
std::tuple< captured_context, void * > operator()( exec_ontop_arg_t, Fn && fn, Args && ... args);

template< typename Fn, typename ... Args >
std::tuple< captured_context, void * > operator()( void * data, exec_ontop_arg_t, Fn && fn, Args && ... args);

Effects:

Same as captured_context::operator(). Additionally, function fn is executed with arguments args in the context of *this (e.g. the stack frame of fn is allocated on stack of *this).

Returns:

The tuple of void pointer argument passed to the most recent call to captured_context::operator(), if any and a captured_context representing the context that has been suspended .

Member function operator==()

bool operator==( captured_context const& other) const noexcept;

Returns:

true if *this and other represent the same execution context, false otherwise.

Throws:

Nothing.

Member function operator!=()

bool operator!=( captured_context const& other) const noexcept;

Returns:

! (other == * this)

Throws:

Nothing.

Member function operator<()

bool operator<( captured_context const& other) const noexcept;

Returns:

true if *this != other is true and the implementation-defined total order of captured_context values places *this before other, false otherwise.

Throws:

Nothing.

Member function operator>()

bool operator>( captured_context const& other) const noexcept;

Returns:

other < * this

Throws:

Nothing.

Member function operator<=()

bool operator<=( captured_context const& other) const noexcept;

Returns:

! (other < * this)

Throws:

Nothing.

Member function operator>=()

bool operator>=( captured_context const& other) const noexcept;

Returns:

! (* this < other)

Throws:

Nothing.

Non-member function operator<<()

template< typename charT, class traitsT >
std::basic_ostream< charT, traitsT > &
operator<<( std::basic_ostream< charT, traitsT > & os, captured_context const& other);

Efects:

Writes the representation of other to stream os.

Returns:

os


PrevUpHomeNext