shared_handle
class is an alternative to
enable_shared_from_this
with some advantanges:
shared_handle
can also instantiate a shared_ptr
that points to a member object. This can be especially suitable in cyclic
references.
enable_shared_from_this
can be cumbersome in some multiple
inheritance scenarios: if a class A
inherits from
enable_shared_from_this<A>
and a class B
inherits from enable_shared_from_this<B>
, then you can't
create a class that inherits both from A
and B
.
shared_handle
doesn't have this kind of problem.
sizeof(shared_handle) < sizeof(enable_shared_from_this<T>)
. Although the difference is small. These values are implementation depended,
but sizeof(shared_handle)
is likely to be equal to
sizeof(void*)
, while
sizeof(enable_shared_from_this<T>)
is likely to be
the twice of that.
shared_handle
imposes the use of
make_shared
or
allocate_shared
, which in turn ensures that the object is aways managed
indeed by shared_ptr
.
This is not necessarily aways an advantage though.
shared_handle
has the disadvange of being
more error prone. Its misuse can lead to dangling pointers.
shared_handle
uses a lower level approach than
enable_shared_from_this
. So it is important to understand
exactly what it does in order to avoid your program to run into some
undefined ( or catastrophic ) behavior.
As you might already know or have deduced, when an object is managed by
shared_ptr
, there must be some sort of other underlying object
that holds the referece counters, the deleter, etc. It is sometimes called
as the "control block". A shared_ptr<T>
instance
internally contains a pointer to the control block and also a pointer
to the T
instance. The shared_ptr<T>
actually never deletes its T
pointer. It just requests the
control block to increment and decrement the reference counter. And the
control block decides when and how to destroy and deallocate the object.
Well, a shared_handle
's instance is internally just a pointer the
control block. The shared_handle::forcibly_assemble_shared_ptr<T>
function instantiates a shared_ptr<T>
by combining the pointer
it takes as argument with the control block this shared_handle
is
associated with. It is the user's responsability to ensure that such combination
is consistent.
An object obtains its corresponding shared_handle
from
make_shared
or
allocate_shared
.
These function templates call the object's constructor passing the
shared_handle
instance as the first argument, and
forward those they receive as the remaining ones.
#include <boost/make_shared.hpp> class X { public: typedef void using_boost_shared_handle; X(boost::shared_handle hdl): m_hdl(hdl) {} boost::shared_ptr<X> f() { return m_hdl.forcibly_assemble_shared_ptr(this); } private: boost::shared_handle m_hdl; }; int main() { boost::shared_ptr<X> p = boost::make_shared<X>(); boost::shared_ptr<X> q = p->f(); assert(p == q); assert(!(p < q || q < p)); // p and q must share ownership }
Usually, make_shared
and allocade_shared
are able to correctly decide which constructor to call, i.e.
whether they shall or not add the shared_handle
to the constructor's argument list. But sometimes this is
not possible, like when the constructor is private, or when the
compiler does not support some SFINAE tricks.
In such cases, to preserve backward compatibilty, the
shared_handle
argument is not added. Unless the
the public member type using_boost_shared_handle
is defined, as above. In this case, the
shared_handle
argument is unconditionally added.
class base_1 { public: base_1(boost::shared_handle hdl): m_hdl(hdl) {} void in_base_1() { shared_ptr<base_1> sp_this = m_hdl.forcibly_assemble_shared_ptr(this); //... } private: boost::shared_handle m_hdl; }; class base_2 { public: base_2(boost::shared_handle hdl): m_hdl(hdl) {} void in_base_2() { shared_ptr<base_2> sp_this = m_hdl.forcibly_assemble_shared_ptr(this); // ... } private: boost::shared_handle m_hdl; }; class derived: public base_1, public base_2 { public: derived(boost::shared_handle hdl): base_1(hdl), base_2(hdl), m_hdl(hdl) { } void in_derived() { shared_ptr<derived> sp_this = m_hdl.forcibly_assemble_shared_ptr(this); // ... } private: boost::shared_handle m_hdl; };
The parameter passed to shared_handle::forcibly_assemble_shared_ptr<T>
does not necessarily has to be the this
pointer.
It could also be a pointer to a composite:
#include <boost/make_shared.hpp> class bar; class foo{ public: foo(bar& b): m_bar(b) {} // ... private: bar& m_bar; }; class bar { public: bar(boost::shared_handle hdl) : m_hdl(hdl) , m_foo(*this) { } boost::shared_ptr<foo> get_foo() { return m_hdl.forcibly_assemble_shared_ptr(&m_foo); } private: boost::shared_handle m_hdl; foo m_foo; }; int main() { boost::shared_ptr<bar> bar_ptr = make_shared<bar>(); boost::shared_ptr<foo> foo_ptr = bar_ptr->get_foo(); // bar_ptr and foo_ptr associated with the same control block: assert(foo_ptr.use_count() == 2); assert(!(bar_ptr < foo_ptr || bar_ptr < foo_ptr)); bar_ptr.reset() // But this bar instance is still alive because of foo_ptr. foo_ptr.reset() // Now the bar instace is destoyed, which in turn causes the destruction of this foo instance too. return 0; }
We created shared_ptr
instances that points to
different object but influence the same use count.
This is not a problem since these objects will be destroyed
together.
If this seem strange to you, note that it similar to when
you instantiate a shared_ptr<A>
from a
shared_ptr<B>
, where A
is
a base class of B
. The
shared_ptr<A>
would point to a sub-object
of the B
instance, that is not very different,
in term of memory managment, than in a composition.
Without shared_handle
, the only way to implement
the function bar::get_foo()
above would be making
bar::m_foo
a shared_ptr<foo>
.
That would not only cost an additional memory allocation, but
would also permit the composite to be destroyed after the owner,
which may be undesirable, especially if the composite references
back the owner, as above. And actually, strictly speaking, that
could not be called a composition anymore.
⚠ Warning:
Remember, shared_ptr
never deletes any pointer.
The control block is the one that invokes the deleter,
and it deletes only the same pointer as the one held by the
shared_ptr
that was returned by make_shared
.
Its your responsability to ensure this deletion in turn resolves
the destruction and deallocation of the other objects whose address
are passed to
shared_handle::forcibly_assemble_shared_ptr<T>
or to shared_handle::forcibly_assemble_weak_ptr<T>
class x { /*...*/ } ; class bad { public: bad(boost::shared_handle hdl) : m_hdl(hdl) { } boost::shared_ptr<x> get_x() { return m_hdl.forcibly_assemble_shared_ptr(new x()); // bad: memory leak. Who will delete this x ? // You'd rather return boost::make_shared<x>() } private: boost::shared_handle m_hdl; };
⚠ Warning:
It is safe to pass a pointer to a composite to
shared_handle::forcibly_assemble_shared_ptr<T>
as long as by compositie you mean an object that is
destroyed when, and only when its owner is destroyed.
You must ensure that the pointers passed to
shared_handle::forcibly_assemble_shared_ptr<T>
or to shared_handle::forcibly_assemble_weak_ptr<T>
will not become dangling before that:
class x{ public: void do_something(); //.. }; class bad { public: bad(boost::shared_handle hdl) : m_hdl(hdl) , m_x(new x()) {} boost::shared_ptr<x> get_x() { return m_hdl.forcibly_assemble_shared_ptr(m_x.get()); } void bad_function() { m_x.clear(); // bad : premature destruction } private: boost::shared_handle m_hdl; std::unique_ptr<x> m_x; }; int main() { boost::shared_ptr<bad> bad_object = boost::make_shared<bad>(); boost::shared_ptr<x> x_object = bad_object->get_x(); bad_object->bad_function(); // Ops, deleting the x instance. x_object->do_something(); // Accessing dangling pointer !!! return 0; }
In the absence of certain C++11 features,
make_shared
and allocate_shared
may fail to inject the shared_handle
instance.
If the compiler does not support either variadic templates
or R-value references, then the shared_handle
is
injected only if the constructor expects no other argument.
And if decltype
or the new C++11 function
specification syntax with trailing return type
(auto foo() -> decltype(expr);
) is not supported,
then your class will probably need the
using_boost_shared_handle
public member type.
The following compilers have shown to support the comprehensive
usage of shared_handle
:
namespace boost { class shared_handle { public: shared_handle(const shared_handle&) BOOST_NOEXCEPT; template <typename T> shared_ptr<T> forcibly_assemble_shared_ptr(T*) const BOOST_NOEXCEPT; template <typename T> weak_ptr<T> forcibly_assemble_weak_ptr(T*) const BOOST_NOEXCEPT; }; }
shared_handle(const shared_handle& hdl) BOOST_NOEXCEPT;
Effects: Constructs a
shared_handle
associated to the same control blockThrows: nothing.
template <typename T> shared_ptr<T> forcibly_assemble_shared_ptr(T* p) const BOOST_NOEXCEPT;
Returns: On success, returns a
shared_ptr<T>
that storesp
and is associated with the same control block of thisshared_handle
. On failure return an emptyshared_ptr<T>
. Failure happens ifp
isNULL
or if it is not possible to increment the use count ( like if the call is done by the destructor of the object that is managed by this control block ).Throws: nothing.
template <typename T> weak_ptr<T> forcibly_assemble_weak_ptr(T* p) const BOOST_NOEXCEPT;
Returns:Same as
weak_ptr<T>(forcibly_assemble_shared_ptr(p))
Throws: nothing.
Copyright Roberto Hinz. 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.