forked from boostorg/asio
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add composed operation examples for c++20.
- Loading branch information
1 parent
3292226
commit 64289a9
Showing
13 changed files
with
2,454 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# | ||
# Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com) | ||
# | ||
# Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
# | ||
|
||
lib socket ; # SOLARIS | ||
lib nsl ; # SOLARIS | ||
lib ws2_32 ; # NT | ||
lib mswsock ; # NT | ||
lib ipv6 ; # HPUX | ||
lib network ; # HAIKU | ||
|
||
project | ||
: requirements | ||
<library>/boost/system//boost_system | ||
<library>/boost/chrono//boost_chrono | ||
<define>BOOST_ALL_NO_LIB=1 | ||
<threading>multi | ||
<target-os>solaris:<library>socket | ||
<target-os>solaris:<library>nsl | ||
<target-os>windows:<define>_WIN32_WINNT=0x0501 | ||
<target-os>windows,<toolset>gcc:<library>ws2_32 | ||
<target-os>windows,<toolset>gcc:<library>mswsock | ||
<target-os>windows,<toolset>gcc-cygwin:<define>__USE_W32_SOCKETS | ||
<target-os>hpux,<toolset>gcc:<define>_XOPEN_SOURCE_EXTENDED | ||
<target-os>hpux:<library>ipv6 | ||
<target-os>haiku:<library>network | ||
; | ||
|
||
exe callback_wrapper : callback_wrapper.cpp ; | ||
exe c_callback_wrapper : c_callback_wrapper.cpp ; | ||
exe composed_1 : composed_1.cpp ; | ||
exe composed_2 : composed_2.cpp ; | ||
exe composed_3 : composed_3.cpp ; | ||
exe composed_4 : composed_4.cpp ; | ||
exe composed_5 : composed_5.cpp ; | ||
exe composed_6 : composed_6.cpp ; | ||
exe composed_7 : composed_7.cpp ; | ||
exe composed_8 : composed_8.cpp ; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
// | ||
// c_callback_wrapper.cpp | ||
// ~~~~~~~~~~~~~~~~~~~~~~ | ||
// | ||
// Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com) | ||
// | ||
// Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
// | ||
|
||
#include <boost/asio.hpp> | ||
#include <iostream> | ||
#include <memory> | ||
#include <new> | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
// This is a mock implementation of a C-based API that uses the function pointer | ||
// plus void* context idiom for exposing a callback. | ||
|
||
void read_input(const char* prompt, void (*cb)(void*, const char*), void* arg) | ||
{ | ||
std::thread( | ||
[prompt = std::string(prompt), cb, arg] | ||
{ | ||
std::cout << prompt << ": "; | ||
std::cout.flush(); | ||
std::string line; | ||
std::getline(std::cin, line); | ||
cb(arg, line.c_str()); | ||
}).detach(); | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
// This is an asynchronous operation that wraps the C-based API. | ||
|
||
// To map our completion handler into a function pointer / void* callback, we | ||
// need to allocate some state that will live for the duration of the | ||
// operation. A pointer to this state will be passed to the C-based API. | ||
template <boost::asio::completion_handler_for<void(std::string)> Handler> | ||
class read_input_state | ||
{ | ||
public: | ||
read_input_state(Handler&& handler) | ||
: handler_(std::move(handler)), | ||
work_(boost::asio::make_work_guard(handler_)) | ||
{ | ||
} | ||
|
||
// Create the state using the handler's associated allocator. | ||
static read_input_state* create(Handler&& handler) | ||
{ | ||
// A unique_ptr deleter that is used to destroy uninitialised objects. | ||
struct deleter | ||
{ | ||
// Get the handler's associated allocator type. If the handler does not | ||
// specify an associated allocator, we will use a recycling allocator as | ||
// the default. As the associated allocator is a proto-allocator, we must | ||
// rebind it to the correct type before we can use it to allocate objects. | ||
typename std::allocator_traits< | ||
boost::asio::associated_allocator_t<Handler, | ||
boost::asio::recycling_allocator<void>>>::template | ||
rebind_alloc<read_input_state> alloc; | ||
|
||
void operator()(read_input_state* ptr) | ||
{ | ||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, ptr, 1); | ||
} | ||
} d{boost::asio::get_associated_allocator(handler, | ||
boost::asio::recycling_allocator<void>())}; | ||
|
||
// Allocate memory for the state. | ||
std::unique_ptr<read_input_state, deleter> uninit_ptr( | ||
std::allocator_traits<decltype(d.alloc)>::allocate(d.alloc, 1), d); | ||
|
||
// Construct the state into the newly allocated memory. This might throw. | ||
read_input_state* ptr = | ||
new (uninit_ptr.get()) read_input_state(std::move(handler)); | ||
|
||
// Release ownership of the memory and return the newly allocated state. | ||
uninit_ptr.release(); | ||
return ptr; | ||
} | ||
|
||
static void callback(void* arg, const char* result) | ||
{ | ||
read_input_state* self = static_cast<read_input_state*>(arg); | ||
|
||
// A unique_ptr deleter that is used to destroy initialised objects. | ||
struct deleter | ||
{ | ||
// Get the handler's associated allocator type. If the handler does not | ||
// specify an associated allocator, we will use a recycling allocator as | ||
// the default. As the associated allocator is a proto-allocator, we must | ||
// rebind it to the correct type before we can use it to allocate objects. | ||
typename std::allocator_traits< | ||
boost::asio::associated_allocator_t<Handler, | ||
boost::asio::recycling_allocator<void>>>::template | ||
rebind_alloc<read_input_state> alloc; | ||
|
||
void operator()(read_input_state* ptr) | ||
{ | ||
std::allocator_traits<decltype(alloc)>::destroy(alloc, ptr); | ||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, ptr, 1); | ||
} | ||
} d{boost::asio::get_associated_allocator(self->handler_, | ||
boost::asio::recycling_allocator<void>())}; | ||
|
||
// To conform to the rules regarding asynchronous operations and memory | ||
// allocation, we must make a copy of the state and deallocate the memory | ||
// before dispatching the completion handler. | ||
std::unique_ptr<read_input_state, deleter> state_ptr(self, d); | ||
read_input_state state(std::move(*self)); | ||
state_ptr.reset(); | ||
|
||
// Dispatch the completion handler through the handler's associated | ||
// executor, using the handler's associated allocator. | ||
boost::asio::dispatch(state.work_.get_executor(), | ||
boost::asio::bind_allocator(d.alloc, | ||
[ | ||
handler = std::move(state.handler_), | ||
result = std::string(result) | ||
]() mutable | ||
{ | ||
std::move(handler)(result); | ||
})); | ||
} | ||
|
||
private: | ||
Handler handler_; | ||
|
||
// According to the rules for asynchronous operations, we need to track | ||
// outstanding work against the handler's associated executor until the | ||
// asynchronous operation is complete. | ||
boost::asio::executor_work_guard< | ||
boost::asio::associated_executor_t<Handler>> work_; | ||
}; | ||
|
||
// The initiating function for the asynchronous operation. | ||
template <boost::asio::completion_token_for<void(std::string)> CompletionToken> | ||
auto async_read_input(const std::string& prompt, CompletionToken&& token) | ||
{ | ||
// Define a function object that contains the code to launch the asynchronous | ||
// operation. This is passed the concrete completion handler, followed by any | ||
// additional arguments that were passed through the call to async_initiate. | ||
auto init = []( | ||
boost::asio::completion_handler_for<void(std::string)> auto handler, | ||
const std::string& prompt) | ||
{ | ||
// The body of the initiation function object creates the long-lived state | ||
// and passes it to the C-based API, along with the function pointer. | ||
using state_type = read_input_state<decltype(handler)>; | ||
read_input(prompt.c_str(), &state_type::callback, | ||
state_type::create(std::move(handler))); | ||
}; | ||
|
||
// The async_initiate function is used to transform the supplied completion | ||
// token to the completion handler. When calling this function we explicitly | ||
// specify the completion signature of the operation. We must also return the | ||
// result of the call since the completion token may produce a return value, | ||
// such as a future. | ||
return boost::asio::async_initiate<CompletionToken, void(std::string)>( | ||
init, // First, pass the function object that launches the operation, | ||
token, // then the completion token that will be transformed to a handler, | ||
prompt); // and, finally, any additional arguments to the function object. | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
void test_callback() | ||
{ | ||
boost::asio::io_context io_context; | ||
|
||
// Test our asynchronous operation using a lambda as a callback. We will use | ||
// an io_context to obtain an associated executor. | ||
async_read_input("Enter your name", | ||
boost::asio::bind_executor(io_context, | ||
[](const std::string& result) | ||
{ | ||
std::cout << "Hello " << result << "\n"; | ||
})); | ||
|
||
io_context.run(); | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
void test_deferred() | ||
{ | ||
boost::asio::io_context io_context; | ||
|
||
// Test our asynchronous operation using the deferred completion token. This | ||
// token causes the operation's initiating function to package up the | ||
// operation with its arguments to return a function object, which may then be | ||
// used to launch the asynchronous operation. | ||
auto op = async_read_input("Enter your name", boost::asio::deferred); | ||
|
||
// Launch our asynchronous operation using a lambda as a callback. We will use | ||
// an io_context to obtain an associated executor. | ||
std::move(op)( | ||
boost::asio::bind_executor(io_context, | ||
[](const std::string& result) | ||
{ | ||
std::cout << "Hello " << result << "\n"; | ||
})); | ||
|
||
io_context.run(); | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
void test_future() | ||
{ | ||
// Test our asynchronous operation using the use_future completion token. | ||
// This token causes the operation's initiating function to return a future, | ||
// which may be used to synchronously wait for the result of the operation. | ||
std::future<std::string> f = | ||
async_read_input("Enter your name", boost::asio::use_future); | ||
|
||
std::string result = f.get(); | ||
std::cout << "Hello " << result << "\n"; | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
int main() | ||
{ | ||
test_callback(); | ||
test_deferred(); | ||
test_future(); | ||
} |
Oops, something went wrong.