问题
When I call boost::asio::ip::tcp::resolver::async_resolve
, my handler receives an ip::tcp::resolver::iterator
that iterates through one or more ip::tcp::resolver::entries
. What is their lifetime, and what is the handle that keeps them alive?
For example, if I get the first entry
and launch a tcp::async_connect
to it, then in the async_connect
handler, can I iterate to the next entry
and launch another async_connect
to the next entry (as long as I pass the iterator
to the async_connect
handler, of course)?
When do the resolver::iterator
and resolver::entries
get cleaned up? Do I have to do anything special, or just let them go out of scope and not held by any callback closure?
(I understand that I could run through all the resolver::entries
in my async_resolve
handler and store them in a smart-pointed structure or whatever, so that I control their lifetime, but if asio::ip::tcp::resolver
is already handling it, my code will be simpler if I just let it do its job.)
回答1:
Deconstructing The Question
Iterators have value semantics. So their lifetime is always bound to the lifetime of the surrounding object, or their storage duration (stack for automatic, heap for dynamic and sometimes even others for e.g. static).
I think you want to know about the validity of the iterators instead.
Well, the documentation shows that the iterator category is forward iterator. Forward iterators have the "Multipass guarantee", allowing repeated dereference of copies of iterators, yielding the same result¹.
So, we're halfway: it could still be ok to keep the iterators. However, of course, like with any other [forward] iterator, we have to think of iterator invalidation.
So, the real question boils down to: when are resolver iterators invalidated.
What Do We Know?
The usecase the resolve
function fulfills is connection. For connection, the first endpoint that works is enough, so there is no need for it to actually retain a list.
In the spirit of pay-for-what-you-need² it would not make sense for resolver to keep the state around longer than required. On the other hand it would not make sense for the iterator to be in the ForwardIterator category if Multipass weren't supported.
The docs say nothing. We have only one recourse: dive into the code
Dive Into The Code
Some steps below the surface we find: asio/detail/resolver_service.hpp:73
// Asynchronously resolve a query to a list of entries.
template <typename Handler>
void async_resolve(implementation_type& impl,
const query_type& query, Handler& handler)
{
// Allocate and construct an operation to wrap the handler.
typedef resolve_op<Protocol, Handler> op;
typename op::ptr p = { boost::asio::detail::addressof(handler),
boost_asio_handler_alloc_helpers::allocate(
sizeof(op), handler), 0 };
resolve_op
shows that the iterator is created using basic_resolver_iterator.hpp::create
And this leads us to the answer: in line 251
typedef std::vector<basic_resolver_entry<InternetProtocol> > values_type;
boost::asio::detail::shared_ptr<values_type> values_;
std::size_t index_;
So, as long as you a keep a copy of a valid the iterator (not the end iterator) you can keep dereferencing it. It will even keep a copy of the query parameters (host_name
and service_name
) with each resolver entry. This seems slightly wasteful but I suppose could come in handy when devising a caching scheme.
SUMMARIZING:
- Q. When are resolver iterators invalidated?
- A. When the last copy of a valid iterator is destructed.
Which translates "they always remain valid" (if they were ever valid).
¹ as opposed to e.g. input iterators
² In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for [The Design and Evolution of C++, 1994]
来源:https://stackoverflow.com/questions/47048485/whats-the-lifetime-of-boostasioiptcpresolveriterator-from-async-resol