forked from boostorg/beast
handler_ptr is move-only:
It is no longer a reference counted object and now has semantics close to a std::unique_ptr. Signed-off-by: Damian Jarek <damian.jarek93@gmail.com>
This commit is contained in:
committed by
Vinnie Falco
parent
200e898f7e
commit
e08132106e
@@ -6,6 +6,7 @@ Version 149:
|
|||||||
* Protect calls from macros
|
* Protect calls from macros
|
||||||
* pausation always allocates
|
* pausation always allocates
|
||||||
* Don't copy completion handlers
|
* Don't copy completion handlers
|
||||||
|
* handler_ptr is move-only
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@@ -10,10 +10,9 @@
|
|||||||
#ifndef BOOST_BEAST_HANDLER_PTR_HPP
|
#ifndef BOOST_BEAST_HANDLER_PTR_HPP
|
||||||
#define BOOST_BEAST_HANDLER_PTR_HPP
|
#define BOOST_BEAST_HANDLER_PTR_HPP
|
||||||
|
|
||||||
|
#include <boost/beast/core/detail/allocator.hpp>
|
||||||
#include <boost/beast/core/detail/config.hpp>
|
#include <boost/beast/core/detail/config.hpp>
|
||||||
#include <boost/beast/core/detail/type_traits.hpp>
|
#include <boost/beast/core/detail/type_traits.hpp>
|
||||||
#include <atomic>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -22,73 +21,63 @@ namespace beast {
|
|||||||
|
|
||||||
/** A smart pointer container with associated completion handler.
|
/** A smart pointer container with associated completion handler.
|
||||||
|
|
||||||
This is a smart pointer that retains shared ownership of an
|
This is a smart pointer that retains unique ownership of an
|
||||||
object through a pointer. Memory is managed using the allocation
|
object through a pointer. Memory is managed using the allocator
|
||||||
and deallocation functions associated with a completion handler,
|
associated with a completion handler stored in the object. The
|
||||||
which is also stored in the object. The managed object is
|
managed object is destroyed and its memory deallocated when one
|
||||||
destroyed and its memory deallocated when one of the following
|
of the following occurs:
|
||||||
happens:
|
|
||||||
|
|
||||||
@li The function @ref invoke is called.
|
@li The function @ref invoke is called.
|
||||||
|
|
||||||
@li The function @ref release_handler is called.
|
@li The function @ref release_handler is called.
|
||||||
|
|
||||||
@li The last remaining container owning the object is destroyed.
|
@li The container is destroyed.
|
||||||
|
|
||||||
Objects of this type are used in the implementation of
|
Objects of this type are used in the implementation of composed
|
||||||
composed operations. Typically the composed operation's shared
|
operations with states that are expensive or impossible to move.
|
||||||
state is managed by the @ref handler_ptr and an allocator
|
This container manages that non-trivial state on behalf of the
|
||||||
associated with the final handler is used to create the managed
|
composed operation.
|
||||||
object.
|
|
||||||
|
|
||||||
@par Thread Safety
|
@par Thread Safety
|
||||||
@e Distinct @e objects: Safe.@n
|
@e Distinct @e objects: Safe.@n
|
||||||
@e Shared @e objects: Unsafe.
|
@e Shared @e objects: Unsafe.
|
||||||
|
|
||||||
@note The reference count is stored using a 16 bit unsigned
|
@tparam T The type of the owned object. Must be noexcept destructible.
|
||||||
integer. Making more than 2^16 copies of one object results
|
|
||||||
in undefined behavior.
|
|
||||||
|
|
||||||
@tparam T The type of the owned object.
|
|
||||||
|
|
||||||
@tparam Handler The type of the completion handler.
|
@tparam Handler The type of the completion handler.
|
||||||
*/
|
*/
|
||||||
template<class T, class Handler>
|
template<class T, class Handler>
|
||||||
class handler_ptr
|
class handler_ptr
|
||||||
{
|
{
|
||||||
struct P
|
T* t_ = nullptr;
|
||||||
{
|
Handler h_;
|
||||||
T* t;
|
|
||||||
std::atomic<std::uint16_t> n;
|
|
||||||
|
|
||||||
// There's no way to put the handler anywhere else
|
void clear();
|
||||||
// without exposing ourselves to race conditions
|
|
||||||
// and all sorts of ugliness.
|
|
||||||
// See:
|
|
||||||
// https://github.com/boostorg/beast/issues/215
|
|
||||||
Handler handler;
|
|
||||||
|
|
||||||
template<class DeducedHandler, class... Args>
|
|
||||||
P(DeducedHandler&& handler, Args&&... args);
|
|
||||||
};
|
|
||||||
|
|
||||||
P* p_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// The type of element this object stores
|
static_assert(std::is_nothrow_destructible<T>::value,
|
||||||
|
"T must be nothrow destructible");
|
||||||
|
|
||||||
|
/// The type of element stored
|
||||||
using element_type = T;
|
using element_type = T;
|
||||||
|
|
||||||
/// The type of handler this object stores
|
/// The type of handler stored
|
||||||
using handler_type = Handler;
|
using handler_type = Handler;
|
||||||
|
|
||||||
/// Copy assignment (disallowed).
|
/// Default constructor (deleted).
|
||||||
|
handler_ptr() = delete;
|
||||||
|
|
||||||
|
/// Copy assignment (deleted).
|
||||||
handler_ptr& operator=(handler_ptr const&) = delete;
|
handler_ptr& operator=(handler_ptr const&) = delete;
|
||||||
|
|
||||||
/** Destructs the owned object if no more @ref handler_ptr link to it.
|
/// Move assignment (deleted).
|
||||||
|
handler_ptr& operator=(handler_ptr &&) = delete;
|
||||||
|
|
||||||
If `*this` owns an object and it is the last @ref handler_ptr
|
/** Destructor
|
||||||
owning it, the object is destroyed and the memory deallocated
|
|
||||||
using the associated deallocator.
|
If `*this` owns an object the object is destroyed and
|
||||||
|
the memory deallocated using the allocator associated
|
||||||
|
with the handler.
|
||||||
*/
|
*/
|
||||||
~handler_ptr();
|
~handler_ptr();
|
||||||
|
|
||||||
@@ -99,54 +88,42 @@ public:
|
|||||||
*/
|
*/
|
||||||
handler_ptr(handler_ptr&& other);
|
handler_ptr(handler_ptr&& other);
|
||||||
|
|
||||||
/// Copy constructor
|
/// Copy constructor (deleted).
|
||||||
handler_ptr(handler_ptr const& other);
|
handler_ptr(handler_ptr const& other) = delete;
|
||||||
|
|
||||||
/** Construct a new @ref handler_ptr
|
/** Constructor
|
||||||
|
|
||||||
This creates a new @ref handler_ptr with an owned object
|
This creates a new container with an owned object of
|
||||||
of type `T`. The allocator associated with the handler will
|
type `T`. The allocator associated with the handler will
|
||||||
be used to allocate memory for the owned object. The constructor
|
be used to allocate memory for the owned object. The
|
||||||
for the owned object will be called thusly:
|
constructor for the owned object will be called with the
|
||||||
|
following equivalent signature:
|
||||||
|
|
||||||
@code
|
@code
|
||||||
T(handler, std::forward<Args>(args)...)
|
T::T(Handler&, Args&&...)
|
||||||
@endcode
|
@endcode
|
||||||
|
|
||||||
@param handler The handler to associate with the owned
|
@param handler The handler to associate with the owned
|
||||||
object. The argument will be moved.
|
object. The argument will be moved if it is an xvalue.
|
||||||
|
|
||||||
@param args Optional arguments forwarded to
|
@param args Optional arguments forwarded to
|
||||||
the owned object's constructor.
|
the owned object's constructor.
|
||||||
*/
|
*/
|
||||||
template<class... Args>
|
template<class DeducedHandler, class... Args>
|
||||||
handler_ptr(Handler&& handler, Args&&... args);
|
explicit handler_ptr(DeducedHandler&& handler, Args&&... args);
|
||||||
|
|
||||||
/** Construct a new @ref handler_ptr
|
/// Returns a const reference to the handler
|
||||||
|
handler_type const&
|
||||||
This creates a new @ref handler_ptr with an owned object
|
handler() const
|
||||||
of type `T`. The allocator associated with the handler will
|
{
|
||||||
be used to allocate memory for the owned object. The constructor
|
return h_;
|
||||||
for the owned object will be called thusly:
|
}
|
||||||
|
|
||||||
@code
|
|
||||||
T(handler, std::forward<Args>(args)...)
|
|
||||||
@endcode
|
|
||||||
|
|
||||||
@param handler The handler to associate with the owned
|
|
||||||
object. The argument will be copied.
|
|
||||||
|
|
||||||
@param args Optional arguments forwarded to
|
|
||||||
the owned object's constructor.
|
|
||||||
*/
|
|
||||||
template<class... Args>
|
|
||||||
handler_ptr(Handler const& handler, Args&&... args);
|
|
||||||
|
|
||||||
/// Returns a reference to the handler
|
/// Returns a reference to the handler
|
||||||
handler_type&
|
handler_type&
|
||||||
handler() const
|
handler()
|
||||||
{
|
{
|
||||||
return p_->handler;
|
return h_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a pointer to the owned object.
|
/** Returns a pointer to the owned object.
|
||||||
@@ -154,32 +131,30 @@ public:
|
|||||||
T*
|
T*
|
||||||
get() const
|
get() const
|
||||||
{
|
{
|
||||||
return p_->t;
|
return t_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a reference to the owned object.
|
/// Return a reference to the owned object.
|
||||||
T&
|
T&
|
||||||
operator*() const
|
operator*() const
|
||||||
{
|
{
|
||||||
return *p_->t;
|
return *t_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a pointer to the owned object.
|
/// Return a pointer to the owned object.
|
||||||
T*
|
T*
|
||||||
operator->() const
|
operator->() const
|
||||||
{
|
{
|
||||||
return p_->t;
|
return t_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Release ownership of the handler
|
/** Release ownership of the handler
|
||||||
|
|
||||||
Requires: `*this` owns an object
|
Requires: `*this` owns an object
|
||||||
|
|
||||||
Before this function returns,
|
Before this function returns, the owned object is
|
||||||
the owned object is destroyed, satisfying the
|
destroyed, satisfying the deallocation-before-invocation
|
||||||
deallocation-before-invocation Asio guarantee. All
|
Asio guarantee.
|
||||||
instances of @ref handler_ptr which refer to the
|
|
||||||
same owned object will be reset, including this instance.
|
|
||||||
|
|
||||||
@return The released handler.
|
@return The released handler.
|
||||||
*/
|
*/
|
||||||
@@ -191,9 +166,7 @@ public:
|
|||||||
This function invokes the handler in the owned object
|
This function invokes the handler in the owned object
|
||||||
with a forwarded argument list. Before the invocation,
|
with a forwarded argument list. Before the invocation,
|
||||||
the owned object is destroyed, satisfying the
|
the owned object is destroyed, satisfying the
|
||||||
deallocation-before-invocation Asio guarantee. All
|
deallocation-before-invocation Asio guarantee.
|
||||||
instances of @ref handler_ptr which refer to the
|
|
||||||
same owned object will be reset, including this instance.
|
|
||||||
|
|
||||||
@note Care must be taken when the arguments are themselves
|
@note Care must be taken when the arguments are themselves
|
||||||
stored in the owned object. Such arguments must first be
|
stored in the owned object. Such arguments must first be
|
||||||
|
@@ -18,87 +18,63 @@ namespace boost {
|
|||||||
namespace beast {
|
namespace beast {
|
||||||
|
|
||||||
template<class T, class Handler>
|
template<class T, class Handler>
|
||||||
template<class DeducedHandler, class... Args>
|
void
|
||||||
inline
|
handler_ptr<T, Handler>::
|
||||||
handler_ptr<T, Handler>::P::
|
clear()
|
||||||
P(DeducedHandler&& h, Args&&... args)
|
|
||||||
: n(1)
|
|
||||||
, handler(std::forward<DeducedHandler>(h))
|
|
||||||
{
|
{
|
||||||
typename std::allocator_traits<
|
typename beast::detail::allocator_traits<
|
||||||
boost::asio::associated_allocator_t<Handler>>::
|
boost::asio::associated_allocator_t<
|
||||||
template rebind_alloc<T> alloc{
|
Handler>>::template rebind_alloc<T> alloc{
|
||||||
boost::asio::get_associated_allocator(handler)};
|
boost::asio::get_associated_allocator(h_)};
|
||||||
t = std::allocator_traits<decltype(alloc)>::allocate(alloc, 1);
|
beast::detail::allocator_traits<
|
||||||
try
|
decltype(alloc)>::destroy(alloc, t_);
|
||||||
{
|
beast::detail::allocator_traits<
|
||||||
t = new(t) T{handler,
|
decltype(alloc)>::deallocate(alloc, t_, 1);
|
||||||
std::forward<Args>(args)...};
|
t_ = nullptr;
|
||||||
}
|
|
||||||
catch(...)
|
|
||||||
{
|
|
||||||
std::allocator_traits<
|
|
||||||
decltype(alloc)>::deallocate(alloc, t, 1);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T, class Handler>
|
template<class T, class Handler>
|
||||||
handler_ptr<T, Handler>::
|
handler_ptr<T, Handler>::
|
||||||
~handler_ptr()
|
~handler_ptr()
|
||||||
{
|
{
|
||||||
if(! p_)
|
if(t_)
|
||||||
return;
|
clear();
|
||||||
if(--p_->n)
|
|
||||||
return;
|
|
||||||
if(p_->t)
|
|
||||||
{
|
|
||||||
p_->t->~T();
|
|
||||||
typename std::allocator_traits<
|
|
||||||
boost::asio::associated_allocator_t<Handler>>::
|
|
||||||
template rebind_alloc<T> alloc{
|
|
||||||
boost::asio::get_associated_allocator(
|
|
||||||
p_->handler)};
|
|
||||||
std::allocator_traits<
|
|
||||||
decltype(alloc)>::deallocate(alloc, p_->t, 1);
|
|
||||||
}
|
|
||||||
delete p_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T, class Handler>
|
template<class T, class Handler>
|
||||||
handler_ptr<T, Handler>::
|
handler_ptr<T, Handler>::
|
||||||
handler_ptr(handler_ptr&& other)
|
handler_ptr(handler_ptr&& other)
|
||||||
: p_(other.p_)
|
: t_(other.t_)
|
||||||
|
, h_(std::move(other.h_))
|
||||||
{
|
{
|
||||||
other.p_ = nullptr;
|
other.t_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T, class Handler>
|
template<class T, class Handler>
|
||||||
|
template<class DeducedHandler, class... Args>
|
||||||
handler_ptr<T, Handler>::
|
handler_ptr<T, Handler>::
|
||||||
handler_ptr(handler_ptr const& other)
|
handler_ptr(DeducedHandler&& handler, Args&&... args)
|
||||||
: p_(other.p_)
|
: t_([&]
|
||||||
|
{
|
||||||
|
BOOST_STATIC_ASSERT(! std::is_array<T>::value);
|
||||||
|
typename beast::detail::allocator_traits<
|
||||||
|
boost::asio::associated_allocator_t<
|
||||||
|
Handler>>::template rebind_alloc<T> alloc{
|
||||||
|
boost::asio::get_associated_allocator(handler)};
|
||||||
|
using A = decltype(alloc);
|
||||||
|
auto const d =
|
||||||
|
[&alloc](T* p)
|
||||||
|
{
|
||||||
|
beast::detail::allocator_traits<A>::deallocate(alloc, p, 1);
|
||||||
|
};
|
||||||
|
std::unique_ptr<T, decltype(d)> p{
|
||||||
|
beast::detail::allocator_traits<A>::allocate(alloc, 1), d};
|
||||||
|
beast::detail::allocator_traits<A>::construct(
|
||||||
|
alloc, p.get(), handler, std::forward<Args>(args)...);
|
||||||
|
return p.release();
|
||||||
|
}())
|
||||||
|
, h_(std::forward<DeducedHandler>(handler))
|
||||||
{
|
{
|
||||||
if(p_)
|
|
||||||
++p_->n;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class T, class Handler>
|
|
||||||
template<class... Args>
|
|
||||||
handler_ptr<T, Handler>::
|
|
||||||
handler_ptr(Handler&& handler, Args&&... args)
|
|
||||||
: p_(new P{std::move(handler),
|
|
||||||
std::forward<Args>(args)...})
|
|
||||||
{
|
|
||||||
BOOST_STATIC_ASSERT(! std::is_array<T>::value);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class T, class Handler>
|
|
||||||
template<class... Args>
|
|
||||||
handler_ptr<T, Handler>::
|
|
||||||
handler_ptr(Handler const& handler, Args&&... args)
|
|
||||||
: p_(new P{handler, std::forward<Args>(args)...})
|
|
||||||
{
|
|
||||||
BOOST_STATIC_ASSERT(! std::is_array<T>::value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T, class Handler>
|
template<class T, class Handler>
|
||||||
@@ -107,18 +83,9 @@ handler_ptr<T, Handler>::
|
|||||||
release_handler() ->
|
release_handler() ->
|
||||||
handler_type
|
handler_type
|
||||||
{
|
{
|
||||||
BOOST_ASSERT(p_);
|
BOOST_ASSERT(t_);
|
||||||
BOOST_ASSERT(p_->t);
|
clear();
|
||||||
p_->t->~T();
|
return std::move(h_);
|
||||||
typename std::allocator_traits<
|
|
||||||
boost::asio::associated_allocator_t<Handler>>::
|
|
||||||
template rebind_alloc<T> alloc{
|
|
||||||
boost::asio::get_associated_allocator(
|
|
||||||
p_->handler)};
|
|
||||||
std::allocator_traits<
|
|
||||||
decltype(alloc)>::deallocate(alloc, p_->t, 1);
|
|
||||||
p_->t = nullptr;
|
|
||||||
return std::move(p_->handler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T, class Handler>
|
template<class T, class Handler>
|
||||||
@@ -127,18 +94,9 @@ void
|
|||||||
handler_ptr<T, Handler>::
|
handler_ptr<T, Handler>::
|
||||||
invoke(Args&&... args)
|
invoke(Args&&... args)
|
||||||
{
|
{
|
||||||
BOOST_ASSERT(p_);
|
BOOST_ASSERT(t_);
|
||||||
BOOST_ASSERT(p_->t);
|
clear();
|
||||||
p_->t->~T();
|
h_(std::forward<Args>(args)...);
|
||||||
typename std::allocator_traits<
|
|
||||||
boost::asio::associated_allocator_t<Handler>>::
|
|
||||||
template rebind_alloc<T> alloc{
|
|
||||||
boost::asio::get_associated_allocator(
|
|
||||||
p_->handler)};
|
|
||||||
std::allocator_traits<
|
|
||||||
decltype(alloc)>::deallocate(alloc, p_->t, 1);
|
|
||||||
p_->t = nullptr;
|
|
||||||
p_->handler(std::forward<Args>(args)...);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // beast
|
} // beast
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include <boost/beast/unit_test/suite.hpp>
|
#include <boost/beast/unit_test/suite.hpp>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace boost {
|
namespace boost {
|
||||||
@@ -22,8 +23,7 @@ class handler_ptr_test : public beast::unit_test::suite
|
|||||||
public:
|
public:
|
||||||
struct handler
|
struct handler
|
||||||
{
|
{
|
||||||
handler() = default;
|
std::unique_ptr<int> ptr;
|
||||||
handler(handler const&) = default;
|
|
||||||
|
|
||||||
void
|
void
|
||||||
operator()(bool& b) const
|
operator()(bool& b) const
|
||||||
@@ -54,12 +54,10 @@ public:
|
|||||||
void
|
void
|
||||||
run() override
|
run() override
|
||||||
{
|
{
|
||||||
handler h;
|
handler_ptr<T, handler> p1{handler{}};
|
||||||
handler_ptr<T, handler> p1{h};
|
|
||||||
handler_ptr<T, handler> p2{p1};
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
handler_ptr<U, handler> p3{h};
|
handler_ptr<U, handler> p2{handler{}};
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
catch(std::exception const&)
|
catch(std::exception const&)
|
||||||
@@ -70,9 +68,9 @@ public:
|
|||||||
{
|
{
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
handler_ptr<T, handler> p4{std::move(h)};
|
handler_ptr<T, handler> p3{handler{}};
|
||||||
bool b = false;
|
bool b = false;
|
||||||
p4.invoke(std::ref(b));
|
p3.invoke(std::ref(b));
|
||||||
BEAST_EXPECT(b);
|
BEAST_EXPECT(b);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user