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:
Damian Jarek
2017-12-02 16:48:05 +01:00
committed by Vinnie Falco
parent 200e898f7e
commit e08132106e
4 changed files with 110 additions and 180 deletions

View File

@@ -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
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@@ -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

View File

@@ -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

View File

@@ -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);
} }
}; };