Search code examples
c++boostshared-ptr

Question on converting boost shared pointer to standard shared pointer


This is more of a follow up question on the second answer posted here. The code from this answer is shown below:

template<typename T>
void do_release(typename boost::shared_ptr<T> const&, T*)
{
}

template<typename T>
typename std::shared_ptr<T> to_std(typename boost::shared_ptr<T> const& p)
{
    return
        std::shared_ptr<T>(
                p.get(),
                boost::bind(&do_release<T>, p, _1));

}

My understanding on above code is, a functor is created from do_release binded with the boost shared_ptr we are trying to convert and is passed in as a custom deleter.

My current thought is (likely wrong): the new standard shared_ptr doesnt hold any existing ref count held by boost shared_ptr but only one ref count for itself after this "conversion". When the standard shared_ptr destructor gets called, it would call the custom deleter which would then trigger the destructor on the boost shared_ptr? So the ref count and life time of the heap resource is still effectively maintained by the boost shared_ptr. What I mean is if the ref count on the boost shared_ptr > 0 (after de-ref by 1) when the custom deleter is called, it would still not destroy the heap memory.

But what if the standard shared_ptr gets copied? Would this conversion still work? I think it would because when the standard share_ptr is copied, it would then increase the ref count, but its the ref count maintained by the standard shared_ptr, so that the overall ref count on the heap resource is still correct. But now the ref count is maintained by both standard + boost shared_ptr?

Am I correct?


Solution

  • The shared pointer created has a destroy function object (deleter) that has state. In particular it has a copy of the boost shared ptr.

    The destruction action does nothing, it is the destruction of deleter that cleans up the boost shared ptr.

    There is a problem though:

    Does the C++ standard fully specify cleanup of the deleter itself? E.g. it might stay around when only weak references remain? The standard does guarantee a minimum lifetime for the deleter, leaving the possibility that lives longer than that.

    Destroying the destruction object is not specified to occur in either the constructor of std::shared_ptr nor the destructor of std::shared_ptr. It would be reasonable to destroy it either after it is called, or when the reference counting block is destroyed - in the second case, we end up with it lasting until the last weak ptr goes away.

    From the draft standard:

    [Note: It is unspecified whether the pointer remains valid longer than that. This can happen if the implementation doesn’t destroy the deleter until all weak_ptr instances that share ownership with p have been destroyed. — end note]

    The possibility to defer destruction of the deleter is clear. So in that case we end up with shared_ptrs that doesn't destruct their element instance when we want it to be called depending on unspecified details of the C++ implementation you run it on. That is a bad idea.

    Better Alternative

    I would personally go with a similar trick based on the aliasing constructor instead. It is just as simple, has no additional overhead, and the semantics are actually correct:

    template<class T>
    std::shared_ptr<T>
    as_std_shared_ptr(boost::shared_ptr<T> bp)
    {
      if (!bp) return nullptr;
      // a std shared pointer to boost shared ptr.  Yes.
      auto pq = std::make_shared<boost::shared_ptr<T>>(std::move(bp));
      // aliasing ctor.  Hide the double shared ptr.  Sneaky.
      return std::shared_ptr<T>(pq, pq.get()->get());
    }
    

    This is much less of a hack than the code from the question, because lifetime of the boost shared ptr is no longer tied to the (unspecified) lifetime of the deleter instance.