Error handling
Synchronous functions
Every synchronous I/O function in Boost.Asio has two overloads in order to deal with errors: the first one throws an exception, and the second one returns an error by reference (return value approach).
When Boost.Asio function throws an exception, it throws an instance of boost::system::system_error
which is inherited from the std::runtime_error
exception class.
To return an error by reference, an instance of boost::system::error_code
is used.
Exception approach
try
{
socket.connect(endpoint);
}
catch(boost::system::system_error const& e)
{
std::cerr << e.what() << "\n";
}
You can retrive the error_code
from the system_error
by calling code()
member function:
catch(boost::system::system_error const& e)
{
boost::system::error_code error = e.code();
std::cerr << error.message() << "\n";
}
Return value approach
If you don't want to mess with exceptions, you can use return value overload:
boost::system::error_code error;
socket.connect(endpoint, error);
if(error)
{
std::cerr << error.message() << "\n";
}
Asynchronous functions
Asynchronous I/O functions don't throw exceptions. They pass boost::system::error_code
into a completion handler instead. So, to check if the operation has been successfully completed you should write something like:
socket.async_connect(endpoint, [&] (boost::system::error_code error)
{
if(!error)
{
// Asynchronous operation has been successfully completed
}
else
{
// Something went wrong
std::cerr << error.message() << "\n";
}
});
error_code
To get a human-readable error description from the boost::system::error_code
you should call message()
member funtion which will return std::string
with the error description.
If you don't want to allocate additional std::string
instance then you can use message(char const* buffer, std::size_t size)
overload.
You can obtain the underlying OS-level error code (which has type int
). To do this call value()
member function.
If a remote peer close the connection then end-of-file error will be generated. In some cases you don't want to treat end-of-file error code as an application-level error. For example, if you receive some seamless data from the remote peer, and the peer close the connection, it may be considered as a normal control flow. In that case you could write:
socket.async_receive(buffer, [&] (boost::system::error_code error, std::size_t bytes_transferred)
{
if(!error)
{
// Asynchronous operation has been successfully completed
// Connection is still alive
}
else if(error == boost::asio::error::eof)
{
// Connection was closed by the remote peer
// Still the buffer holds "bytes_transferred" bytes of the received data
}
else
{
// Something went wrong
std::cerr << error.message() << "\n";
}
});
Should you pass boost::system::error_code
into functions by value or by reference? If you look at Boost.Asio documentation then you'll see that the library's author use pass-by-reference approach. However, currentry error_code
object consists of one int
, one bool
and one raw pointer. It is adviced to pass std::string_view
by value, which consists of one std::size_t
and one raw pointer, which should have the same size (or almost the same size, depends on the target platform) as boost::system::error_code
. So, it's really up to you. In my own code I pass boost::system::error_code
by value.