...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Each instance of coroutine has its own context of execution (CPU registers and stack space) or represents not-a-coroutine (similar to boost::thread). Objects of type coroutine are moveable but not copyable and can be returned by a function.
boost::coroutines::coroutine< void() > f(); void g() { boost::coroutines::coroutine< void() > c( f() ); c(); }
Note | |
---|---|
Boost.Move is used to emulate rvalue references. |
A new coroutine is created from a coroutine-function (function or functor) which will be executed in a new context (CPU registers and stack space).
Note | |
---|---|
coroutine-function is required to return void and accept a reference of type boost::coroutines::coroutine<>::caller_type. |
The template argument Signature determines the data-types transferred to coroutine-function and from coroutine-function by calling boost::coroutines::coroutine<>::operator() and boost::coroutines::coroutine<>::get().
typedef boost::coroutines::coroutine< int( std::string const&) > coro_t; // void f( boost::coroutine< std::string const&( int) > & ca) void f( coro_t::caller_type & ca) { ... // access argument std::string str( ca.get() ); ... ca( 7); ... } std::string str; ... coro_t c( f); // pass argument c( str); // returned value int res = c.get();
The coroutine-function is started at coroutine construction (similar to boost::thread) in a newly created coroutine complete with registers, flags, stack and instruction pointer. If coroutine-function requires some arguments (types defined by Signature) on start-up those parameters must be applied to the coroutine constructor. A single arguments can be passed as it is:
typedef boost::coroutines::coroutine< int( std::string const&) > coro_t; // void f( boost::coroutine< std::string const&( int) > & ca) void f( coro_t::caller_type & ca); std::string str("abc"); coro_t c( f, str);
For multiple arguments boost::coroutines::coroutine<>::arguments must be used (it is a typedef of boost::tuple<>):
typedef boost::coroutines::coroutine< int( int, std::string const&) > coro_t; // void f( boost::coroutine< boost::tuple< int, std::string const& >( int) > & ca) void f( coro_t::caller_type & ca); std::string str("abc"); coro_t c( f, coro_t::arguments( 7, str) );
Note | |
---|---|
The maximum number of arguments is limited to 10 (limit defined by Boost.Tuple). |
Note | |
---|---|
Parameters bound with boost::bind() to coroutine-function will not be part of the boost::coroutines::coroutine<>::operator() signature. |
boost::coroutines::attributes, an additional constructor argument of coroutine, defines the stack size, stack unwinding and floating-point preserving behaviour used for context construction.
The coroutine constructor uses the stack-allocator concept to allocate an associated stack, and the destructor uses the same stack-allocator concept to deallocate the stack. The default stack-allocator concept is stack-allocator, but a custom stack-allocator can be passed to the constructor.
The execution control is transferred to coroutine at construction (coroutine-function entered) - when control should be returned to the original calling routine, invoke boost::coroutines::coroutine<>::operator() on the first argument of type boost::coroutines::coroutine<>::caller_type inside coroutine-function. boost::coroutines::coroutine<>::caller_type is a typedef of coroutine with an inverted Signature. Inverted Signature means that the return type becomes an argument and vice versa. Multiple arguments are wrapped into boost::tuple<>.
void f( boost::coroutines::coroutine< std::string const&( int) & ca); boost::coroutines::coroutine< int( std::string const&) > c1( f); void g( boost::coroutines::coroutine< boost::tuple< X, Y >( int) & ca); boost::coroutines::coroutine< int( X, X) > c2( g);
The current coroutine information (registers, flags, and stack and instruction pointer) is saved and the original context information is restored. Calling boost::coroutines::coroutine<>::operator() resumes execution in the coroutine after saving the new state of the original routine.
typedef boost::coroutines::coroutine< void() > coro_t; // void fn( boost::coroutines::coroutine< void() > & ca, int j) void fn( coro_t::caller_type & ca, int j) { for( int i = 0; i < j; ++i) { std::cout << "fn(): local variable i == " << i << std::endl; // save current coroutine // value of local variable is preserved // transfer execution control back to main() ca(); // coroutine<>::operator()() was called // execution control transferred back from main() } } int main( int argc, char * argv[]) { // bind parameter '7' to coroutine-fn coro_t c( boost::bind( fn, _1, 7) ); std::cout << "main() starts coroutine c" << std::endl; while ( c) { std::cout << "main() calls coroutine c" << std::endl; // execution control is transferred to c c(); } std::cout << "Done" << std::endl; return EXIT_SUCCESS; } output: main() starts coroutine c fn(): local variable i == 0 main() calls coroutine c fn(): local variable i == 1 main() calls coroutine c fn(): local variable i == 2 main() calls coroutine c fn(): local variable i == 3 main() calls coroutine c fn(): local variable i == 4 main() calls coroutine c fn(): local variable i == 5 main() calls coroutine c fn(): local variable i == 6 main() calls coroutine c Done
Warning | |
---|---|
Calling boost::coroutines::coroutine<>::operator() from inside the same coroutine results in undefined behaviour. |
Signature, the template argument of coroutine, defines the types transferred to and returned from the coroutine-function, e.g. it determines the signature of boost::coroutines::coroutine<>::operator() and the return-type of boost::coroutines::coroutine<>::get().
Note | |
---|---|
boost::coroutines::coroutine<>::caller_type is not part of Signature and coroutine-function is required to return void and accept boost::coroutines::coroutine<>::caller_type as argument. |
boost::coroutines::coroutine<>::operator() accepts arguments as defined in Signature and returns a reference to coroutine. The arguments passed to boost::coroutines::coroutine<>::operator(), in one coroutine, is returned (as a boost::tuple<>) by boost::coroutines::coroutine<>::get() in the other coroutine. If coroutine is constructed and arguments are passed to the constructor, the coroutine-function will be entered and the arguments are accessed thorough boost::coroutines::coroutine<>::get() in coroutine-function on entry.
The value given to boost::coroutines::coroutine<>::operator() of boost::coroutines::coroutine<>::caller_type, in one coroutine, is returned by boost::coroutines::coroutine<>::get() in the other routine.
typedef boost::coroutines::coroutine< int( int) > coro_t; // void fn( boost::coroutines::coroutine< int( int) > & ca) void fn( coro_t::caller_type & ca) { // access the integer argument given to coroutine ctor int i = ca.get(); std::cout << "fn(): local variable i == " << i << std::endl; // save current coroutine context and // transfer execution control back to caller // pass content of variable 'i' to caller // after execution control is returned back coroutine<>::operator() // returns and the transferred integer s accessed via coroutine<>::get() i = ca( i).get(); // i == 10 because c( 10) in main() std::cout << "fn(): local variable i == " << i << std::endl; ca( i); } int main( int argc, char * argv[]) { std::cout << "main(): call coroutine c" << std::endl; coro_t c( fn, 7); int x = c.get(); std::cout << "main(): transferred value: " << x << std::endl; x = c( 10).get(); std::cout << "main(): transferred value: " << x << std::endl; std::cout << "Done" << std::endl; return EXIT_SUCCESS; } output: main(): call coroutine c fn(): local variable i == 7 main(): transferred value: 7 fn(): local variable i == 10 main(): transferred value: 10 Done
If coroutine-function has more than one argument boost::coroutines::coroutine<>::operator() has the same size of arguments and boost::coroutines::coroutine<>::get() from boost::coroutines::coroutine<>::caller_type returns a boost::tuple<> corresponding to the arguments of Signature. boost::tie helps to access the values stored in the boost::tuple<> returned by boost::coroutines::coroutine<>::get().
typedef boost::coroutines::coroutine< int(int,int) > coro_t; // void fn( boost::coroutines::coroutine< boost::tuple< int, int >( int) > & ca) void fn( coro_t::caller_type & ca) { int a, b; boost::tie( a, b) = ca.get(); boost::tie( a, b) = ca( a + b).get(); ca( a + b); } int main( int argc, char * argv[]) { std::cout << "main(): call coroutine c" << std::endl; coro_t coro( fn, coro_t::arguments( 3, 7) ); int res = coro.get(); std::cout << "main(): 3 + 7 == " << res << std::endl; res = coro( 5, 7).get(); std::cout << "main(): 5 + 7 == " << res << std::endl; std::cout << "Done" << std::endl; return EXIT_SUCCESS; } output: main(): call coroutine c main(): 3 + 7 == 10 main(): 5 + 7 == 12 Done
You can transfer references and pointers from and to coroutines but as usual
you must take care (scope, no re-assignment of const references etc.). In the
following code x
points to
local
which is allocated on
stack of c
. When c
goes out of scope the stack becomes deallocated.
Using x
after c
is gone will fail!
struct X { void g(); }; typedef boost::coroutines::coroutine< X*() > coro_t; // void fn( boost::coroutines::coroutine< void( X*) > & ca) void fn( coro_t::caller_t & ca) { X local; ca( & local); } int main() { X * x = 0; { coro_t c( fn); x = c.get(); // let x point to X on stack owned by c // stack gets unwound -> X will be destructed } x->g(); // segmentation fault! return EXIT_SUCCESS; }
Boost.Coroutine provides output- and input-iterators
using Boost.Range. coroutine< T()
>
can be used via output-iterators
using boost::begin() and boost::end().
typedef boost::coroutines::coroutine< int() > coro_t; typedef boost::range_iterator< coro_t >::type iterator_t; // void power( boost::coroutines::coroutine< void( int) > & ca, int number, int exponent) void power( coro_t::caller_type & ca, int number, int exponent) { int counter = 0; int result = 1; while ( counter++ < exponent) { result = result * number; ca( result); } } int main() { coro_t c( boost::bind( power, _1, 2, 8) ); iterator_t e( boost::end( c) ); for ( iterator_t i( boost::begin( c) ); i != e; ++i) std::cout << * i << " "; std::cout << "\nDone" << std::endl; return EXIT_SUCCESS; } output: 2 4 8 16 32 64 128 256 Done
BOOST_FOREACH
can be used to
iterate over the coroutine range too.
typedef boost::coroutines::coroutine< int() > coro_t; typedef boost::range_iterator< coro_t >::type iterator_t; // void power( boost::coroutines::coroutine< void( int) > & ca, int number, int exponent) void power( coro_t::caller_type & ca, int number, int exponent) { int counter = 0; int result = 1; while ( counter++ < exponent) { result = result * number; ca( result); } } int main() { coro_t c( boost::bind( power, _1, 2, 8) ); BOOST_FOREACH( int i, c) { std::cout << i << " "; } std::cout << "\nDone" << std::endl; return EXIT_SUCCESS; } output: 2 4 8 16 32 64 128 256 Done
Input iterators are created from coroutines of type coroutine< void(
T) >
.
coroutine-function is exited with a simple return statement jumping back to the calling routine. The coroutine becomes complete, e.g. boost::coroutines::coroutine<>::operator bool will return 'false'.
typedef boost::coroutines::coroutine< int(int,int) > coro_t; // void power( boost::coroutines::coroutine< boost::tuple< int, int >( int) > & ca, int number, int exponent) void fn( coro_t::caller_type & ca) { int a, b; boost::tie( a, b) = ca.get(); boost::tie( a, b) = ca( a + b).get(); ca( a + b); } int main( int argc, char * argv[]) { std::cout << "main(): call coroutine c" << std::endl; coro_t coro( fn, coro_t::arguments( 3, 7) ); BOOST_ASSERT( coro); int res = coro.get(); std::cout << "main(): 3 + 7 == " << res << std::endl; res = coro( 5, 7).get(); BOOST_ASSERT( ! coro); std::cout << "main(): 5 + 7 == " << res << std::endl; std::cout << "Done" << std::endl; return EXIT_SUCCESS; } output: main(): call coroutine c main(): 3 + 7 == 10 main(): 5 + 7 == 12 Done
Important | |
---|---|
After returning from coroutine-function the coroutine is complete (can not resumed with boost::coroutines::coroutine<>::operator()). |
An exception thrown inside coroutine-function will transferred via exception-pointer (see Boost.Exception for details) and re-thrown by constructor or boost::coroutines::coroutine<>::operator().
typedef boost::coroutines::coroutine< void() > coro_t; // void fn( boost::coroutines::coroutine< void() > & ca) void fn( coro_t::caller_type & ca) { ca(); throw std::runtime_error("abc"); } int main( int argc, char * argv[]) { coro_t c( f); try { c(); } catch ( std::exception const& e) { std::cout << "exception catched:" << e.what() << std::endl; return EXIT_FAILURE; } std::cout << "Done" << std::endl; return EXIT_SUCCESS; } output: exception catched: abc
Important | |
---|---|
Code executed by coroutine must not prevent the propagation of the boost::coroutines::detail::forced_unwind exception. Absorbing that exception will cause stack unwinding to fail. Thus, any code that catches all exceptions must re-throw the pending exception. |
try { // code that might throw } catch( forced_unwind) { throw; } catch(...) { // possibly not re-throw pending exception }
Sometimes it is necessary to unwind the stack of an unfinished coroutine to
destroy local stack variables so they can release allocated resources (RAII
pattern). The third argument of the coroutine constructor, do_unwind
,
indicates whether the destructor should unwind the stack (stack is unwound
by default).
Stack unwinding assumes the following preconditions:
After unwinding, a coroutine is complete.
typedef boost::coroutines::coroutine< void() > coro_t; struct X { X() { std::cout << "X()" << std::endl; } ~X() { std::cout << "~X()" << std::endl; } }; // void fn( boost::coroutines::coroutine< void() > & ca) void fn( coro_t::caller_type & ca) { X x; for ( int = 0;; ++i) { std::cout << "fn(): " << i << std::endl; // transfer execution control back to main() ca(); } } int main( int argc, char * argv[]) { { coro_t c( fn, boost::coroutines::attributes( boost::ctx::default_stacksize(), boost::coroutines::stack_unwind) ); c(); c(); c(); c(); c(); std::cout << "c is complete: " << std::boolalpha << c.is_complete() << "\n"; } std::cout << "Done" << std::endl; return EXIT_SUCCESS; } output: X() fn(): 0 fn(): 1 fn(): 2 fn(): 3 fn(): 4 fn(): 5 c is complete: false ~X() Done
Important | |
---|---|
You must not swallow boost::coroutines::detail::forced_unwind exceptions! |
Some applications do not use floating-point registers and can disable preserving fpu registers for performance reasons.
Note | |
---|---|
According to the calling convention the FPU registers are preserved by default. |