boost.png (6897 bytes)shared_handle class

shared_handle class is an alternative to enable_shared_from_this with some advantanges:

On the other hand, shared_handle has the disadvange of being more error prone. Its misuse can lead to dangling pointers.

How it works

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.

Example with multiple inheritance

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

Composite objects

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

Supported Compilers

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:


Synopsis

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

Members

copy constructor

shared_handle(const shared_handle& hdl) BOOST_NOEXCEPT;

Effects: Constructs a shared_handle associated to the same control block

Throws: nothing.

forcibly_assemble_shared_ptr

template <typename T> shared_ptr<T> forcibly_assemble_shared_ptr(T* p) const BOOST_NOEXCEPT;

Returns: On success, returns a shared_ptr<T> that stores p and is associated with the same control block of this shared_handle. On failure return an empty shared_ptr<T>. Failure happens if p is NULL 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.

forcibly_assemble_weak_ptr

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.