Home > front end >  Question on converting boost shared pointer to standard shared pointer
Question on converting boost shared pointer to standard shared pointer

Time:03-23

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?

CodePudding user response:

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.

CodePudding user response:

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

Indeed.

You can just see it for yourself.

CAUTION

The remainder only anecdotally demonstrates behaviour in limited scenarios, on particular implementations.

As @Yakk's answer points out, there is implementation-defined behaviour (unspecified behaviour) will lead to problems. Therefore, I recommend his superior alternative.

I'll use a more modern spelling with lambda instead of bind:

template <typename T> auto to_std(boost::shared_ptr<T> const& p) {
    return std::shared_ptr<T>(p.get(), [p](auto) {});
}

Here's a good demonstration:

Live On Coliru

#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <memory>

template <typename T> auto to_std(boost::shared_ptr<T> const& p) {
    return std::shared_ptr<T>(p.get(), [p](auto) {});
}

struct X {
    ~X() { std::cout << "X::~X()\n"; }
};

int main() {
    auto b = boost::make_shared<X>();
    auto s = to_std(b);

    auto report = [&] {
        std::cout << "boost: " << b.use_count() << "\tstd: " << s.use_count() << "\n";
    };

    report();

    auto b2 = b;
    report();

    std::vector bb(42, b2);
    report();

    auto s2 = s;
    report();

    std::vector ss(17, s2);
    report();

    bb.clear();
    b2.reset();
    report();

    ss.clear();
    report();

    b.reset();
    s.reset();
    report();

    std::cout << "but s2 is still there: " << s2.use_count() << "\n";
    s2.reset();
}

Prints

boost: 2    std: 1
boost: 3    std: 1
boost: 45   std: 1
boost: 45   std: 2
boost: 45   std: 19
boost: 2    std: 19
boost: 2    std: 2
boost: 0    std: 0
but s2 is still there: 1
X::~X()

CodePudding user response:

If you will modify the custom deleter as below then it will behave as you mentioned and the custom deleter deletes the pointer. But in the original version, it does nothing.

template<typename T>
void do_release(typename boost::shared_ptr<T> const& p1, T*)
{
    delete p1.get();
}

The standard pointer will keep only its own reference count. The boost pointer will increase use_count when the standard pointer also points to it. After execution of custom deleter boost will again reduce the use_count by one.

  • Related