Conversion from Boost::Shared_Ptr to Std::Shared_Ptr

Conversion from boost::shared_ptr to std::shared_ptr?

Update to an "off the top of my head" answer, almost eleven years later:

As pointed out in this answer, an implementation is allowed to extend the lifetime the deleter beyond the lifetime of the shared_ptr. For example, until after all weak_ptr instances are also destroyed. This would cause a problem where the presence of weak_ptr instances prevent the destruction of the underlying object, obviously a problem.

To avoid this, either use the approach in the answer by @Fozi with an explicit call to reset(), or the aliasing constructor approach in the linked answer.

Original Answer:

You can carry the boost::shared_ptr "inside" the std::shared_ptr by using the destructor to carry the reference around:

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

}

The only real reason to do this is if you have a bunch of code that expects std::shared_ptr<T>.

Cohabitation of boost::shared_ptr and std::shared_ptr

You could do it like this:

template<typename T>
boost::shared_ptr<T> make_shared_ptr(std::shared_ptr<T>& ptr)
{
return boost::shared_ptr<T>(ptr.get(), [ptr](T*) mutable {ptr.reset();});
}

template<typename T>
std::shared_ptr<T> make_shared_ptr(boost::shared_ptr<T>& ptr)
{
return std::shared_ptr<T>(ptr.get(), [ptr](T*) mutable {ptr.reset();});
}

EDIT: Note that this does not work with weak references to the source ptr. So be careful with those!

Question on converting boost shared pointer to standard shared pointer

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.

Error converting form boost::shared_ptrT to std::shared_ptrT

Here's a small example of where the error comes from:

struct abstract
{
virtual void foo() = 0;
};

template<class X, class Y>
struct templ {};

template<class T>
struct bar
{
template<class U>
bar(templ<U[], T[]>) {} // (A)
};

int main()
{
bar<abstract> x;
}

It seems even to be illegal to form the type array of [abstract-type] in (A), therefore instantiating the class template bar with the argument abstract makes the program ill-formed.


The same thing is happening in the background for shared_array. But why is shared_array<Base> instantiated?

The free function boost::get_deleter is overloaded, shared_array.hpp adds an overload to the overload set (actually, adds a template):

template< class D, class T > D * get_deleter( shared_array<T> const & p );

Before overload resolution, even before finding out which functions are viable, function templates need to be instantiated. Instantiating this get_deleter template above leads to instantiating shared_array<Base>, which leads to the program being ill-formed.

The solution is, not to let the above get instantiated: Don't supply the template parameter T, it can't deduce the T in shared_array<T> from a shared_ptr<T>: the two types are unrelated.

Change

if(H *h = boost::get_deleter<H, T>(p))

to

if(H *h = boost::get_deleter<H>(p))

and it works.


Explanation why letting T be deduced works:

When writing a function call where a function template could be meant (looking at the name called), the template parameters have to be set. You can supply them explicitly (inside <> as in get_deleter<H, T>). If you don't supply all of them explicitly (as in get_deleter<H>), the remaining ones have to be deduced from the arguments of the function call.

After all template parameters have been either set explicitly or deduced, their occurrences in the function template are substituted. The error when using get_deleter<H, Derived> occurs in this step: the substituted get_deleter looks like this:

template<> H * get_deleter( shared_array<Derived> const & p );

Here, shared_array<Derived> needs to be instantiated. But during this instantiation, the error explained above occurs. (Note: it's not in the immediate context of get_deleter, therefore SFINAE doesn't apply.)

Now, when you don't explicitly supply the second template parameter, it has to be deduced. And this deduction fails for the function template

template< class D, class T > D * get_deleter( shared_array<T> const & p );

if you use an argument expression of type shared_ptr<Derived>. As deduction fails, no instantiation takes place of the function template, and therefore no instantiation of shared_array<Derived> (deduction fails = some template parameters could not be set).

Why does deduction fail? The compiler needs to deduce the template parameter T inside the function parameter type shared_array<T> const& from the argument expression, which is of the type shared_ptr<Derived>. But this deduction can only succeed (with few exceptions) when the function parameter type can be made equal to the argument expression type. I.e., it can only succeed if there's some type X, such that shared_array<X> is the same type as shared_ptr<Derived>. But there isn't: the specializations of shared_ptr and shared_array are unrelated.

Therefore, the template parameter T of this get_deleter overload cannot be deduced; therefore this function template isn't instantiated, and shared_array<Derived> isn't instantiated.

Deduction failure is a special kind of failure (like SFINAE): It doesn't lead to the program being ill-formed (i.e. it doesn't lead to a compilation error). Rather, the function template for which deduction didn't succeed simply doesn't add a function to the overload set. If there are other functions in the overload set, one of those can be called instead.

Another function template

template<class D, class T> D * get_deleter( shared_ptr<T> const & p )

from boost/smart_ptr/shared_ptr.hpp runs through the same process. However, deduction succeeds here, and a specialization get_deleter<H, T> (with the H and T from to_std_ptr) is added to the overload set. It'll later be chosen by overload resolution.

convert const std::shared_ptrconst T into boost::shared_ptrT

You cannot transfer ownership from std::shared_ptr to boost::shared_ptr.
You might from a std::unique_ptr though.

But you can create a boost::shared_ptr with a custom deleter.

boost::shared_ptr<io_adaptor::msg::PandarScan>
msg_boost(const_cast<io_adaptor::msg::PandarScan*>(msg.get()),
[msg = msg](auto*) mutable { msg.reset(); });

Demo

Deleter captures original shared_ptr to maintain the lifetime,
and instead of releasing the resource, it just "decreases" the refcount.

C++ - passing references to std::shared_ptr or boost::shared_ptr

The point of a distinct shared_ptr instance is to guarantee (as far as possible) that as long as this shared_ptr is in scope, the object it points to will still exist, because its reference count will be at least 1.

Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
// sp points to an object that cannot be destroyed during this function
}

So by using a reference to a shared_ptr, you disable that guarantee. So in your second case:

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{
...
sp->do_something();
...
}

How do you know that sp->do_something() will not blow up due to a null pointer?

It all depends what is in those '...' sections of the code. What if you call something during the first '...' that has the side-effect (somewhere in another part of the code) of clearing a shared_ptr to that same object? And what if it happens to be the only remaining distinct shared_ptr to that object? Bye bye object, just where you're about to try and use it.

So there are two ways to answer that question:

  1. Examine the source of your entire program very carefully until you are sure the object won't die during the function body.

  2. Change the parameter back to be a distinct object instead of a reference.

General bit of advice that applies here: don't bother making risky changes to your code for the sake of performance until you've timed your product in a realistic situation in a profiler and conclusively measured that the change you want to make will make a significant difference to performance.

Update for commenter JQ

Here's a contrived example. It's deliberately simple, so the mistake will be obvious. In real examples, the mistake is not so obvious because it is hidden in layers of real detail.

We have a function that will send a message somewhere. It may be a large message so rather than using a std::string that likely gets copied as it is passed around to multiple places, we use a shared_ptr to a string:

void send_message(std::shared_ptr<std::string> msg)
{
std::cout << (*msg.get()) << std::endl;
}

(We just "send" it to the console for this example).

Now we want to add a facility to remember the previous message. We want the following behaviour: a variable must exist that contains the most recently sent message, but while a message is currently being sent then there must be no previous message (the variable should be reset before sending). So we declare the new variable:

std::shared_ptr<std::string> previous_message;

Then we amend our function according to the rules we specified:

void send_message(std::shared_ptr<std::string> msg)
{
previous_message = 0;
std::cout << *msg << std::endl;
previous_message = msg;
}

So, before we start sending we discard the current previous message, and then after the send is complete we can store the new previous message. All good. Here's some test code:

send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);

And as expected, this prints Hi! twice.

Now along comes Mr Maintainer, who looks at the code and thinks: Hey, that parameter to send_message is a shared_ptr:

void send_message(std::shared_ptr<std::string> msg)

Obviously that can be changed to:

void send_message(const std::shared_ptr<std::string> &msg)

Think of the performance enhancement this will bring! (Never mind that we're about to send a typically large message over some channel, so the performance enhancement will be so small as to be unmeasureable).

But the real problem is that now the test code will exhibit undefined behaviour (in Visual C++ 2010 debug builds, it crashes).

Mr Maintainer is surprised by this, but adds a defensive check to send_message in an attempt to stop the problem happening:

void send_message(const std::shared_ptr<std::string> &msg)
{
if (msg == 0)
return;

But of course it still goes ahead and crashes, because msg is never null when send_message is called.

As I say, with all the code so close together in a trivial example, it's easy to find the mistake. But in real programs, with more complex relationships between mutable objects that hold pointers to each other, it is easy to make the mistake, and hard to construct the necessary test cases to detect the mistake.

The easy solution, where you want a function to be able to rely on a shared_ptr continuing to be non-null throughout, is for the function to allocate its own true shared_ptr, rather than relying on a reference to an existing shared_ptr.

The downside is that copied a shared_ptr is not free: even "lock-free" implementations have to use an interlocked operation to honour threading guarantees. So there may be situations where a program can be significantly sped up by changing a shared_ptr into a shared_ptr &. But it this is not a change that can be safely made to all programs. It changes the logical meaning of the program.

Note that a similar bug would occur if we used std::string throughout instead of std::shared_ptr<std::string>, and instead of:

previous_message = 0;

to clear the message, we said:

previous_message.clear();

Then the symptom would be the accidental sending of an empty message, instead of undefined behaviour. The cost of an extra copy of a very large string may be a lot more significant than the cost of copying a shared_ptr, so the trade-off may be different.

Can't created a boost::shared_ptr from this

Since setupArguments is a const-qualified function, this is of type const A*. And const A* is not convertible to A* so it fails to compile.

You would need to use shared_ptr<const A> or remove the const qualifier.

The full error given should look like:

error C2440: 'initializing': cannot convert from 'Y *' to 'A *'
with
[
Y=const A
]
note: Conversion loses qualifiers

OT: be careful with constructing a shared_ptr from this, as it may lead to double deletes. Consider using boost::enable_shared_from_this

how to convert char * to boost::shared_ptr?

You cannot convert a string literal to a shared pointer. Let me just "correct" your code, and then all you have remaining is undefined behavior:

const char *str = "abcdfg";
boost::shared_ptr<char> ss(str);

Now, this will compile, but it will produce serious problems because str is not memory that was allocated dynamically. As soon as the shared pointer is destructed, you will get undefined behavior.

So, if you want that string, you will have to copy it first:

const char *str = "abcdfg";
boost::shared_ptr<char> ss( new char[std::strlen(str)+1] );
std::strcpy( ss.get(), str );

But if you're just doing this to have RAII semantics on a string, why not use std::string in the first place?

std::string sss( str );

boost::shared_ptr and std::shared_ptr co-existence

You can just pull them into a custom namespace

#ifdef HAS_STD_SHARED_PTR
#include <memory>
#define SHARED_PTR_NAMESPACE std
#else
#include <boost/shared_ptr.hpp>
#define SHARED_PTR_NAMESPACE boost
#endif

namespace my_namespace {
using SHARED_PTR_NAMESPACE::shared_ptr;
using SHARED_PTR_NAMESPACE::make_shared;
}

#undef SHARED_PTR_NAMESPACE

The HAS_STD_SHARED_PTR flag needs to be set by the build environment. CMake provides several mechanisms for detecting such features and newer C++ implementations also provide feature test macros for quick and easy detection of newer features.



Related Topics



Leave a reply



Submit