Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 681
somewhat_clueless_developer Avatar asked Mar 09 '26 11:03

somewhat_clueless_developer


2 Answers

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.

like image 94
Yakk - Adam Nevraumont Avatar answered Mar 11 '26 03:03

Yakk - Adam Nevraumont


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()
like image 26
sehe Avatar answered Mar 11 '26 01:03

sehe



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!