47

I want to use boost::log at some point, but I cannot pass a std::shared_ptr as a parameter, because the compiler (VS2010) cannot convert it into a boost::shared_ptr.

I don't really like the fact that they are aliens to one another.

Is there a safe and transparent way to convert one into the another, so as they don't stumble over each other?

I don't think it is duplicate of this question that states both are the same.

Community
  • 1
  • 1
Stephane Rolland
  • 38,876
  • 35
  • 121
  • 169
  • For a pre-C++11 answer, see: http://stackoverflow.com/questions/6326757/conversion-from-boostshared-ptr-to-stdshared-ptr/6327305 – janm Sep 07 '12 at 10:27
  • I have a similar problem with std::array vs. boost::array. – alfC Sep 09 '12 at 02:22
  • @alfC I don't think that there's an easy way to convert between `std::array` and `boost::array` w/o copying the contents of the array. – Marshall Clow Oct 12 '13 at 20:33
  • Maybe in the case of `std::shared_ptr` and `boost::shared_ptr` (and `std::array` and `boost::array`) one can do a `reinterpret_cast`? – alfC Oct 13 '13 at 00:54
  • 3
    @alfC: even if that gets the compiler to stop complaining, it's a surefire way to corrupt the smart pointer state. – Michael Burr Jan 06 '14 at 18:18
  • 1
    They should have the same inerface, why not hack boost, and replace content of `boost/shared_ptr.hpp` with something like `#include namespace boost { using shared_ptr = std::shared_ptr`? – fghj Nov 19 '15 at 21:54

1 Answers1

74

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!

ronag
  • 49,529
  • 25
  • 126
  • 221
  • 2
    What on earth is the second parameter to the constructors? It looks (from the constructor signature) like it should be a deleter, but I don't understand the syntax. – Chowlett Sep 07 '12 at 09:14
  • 1
    It's a lambda expression for a function that does nothing. – jcoder Sep 07 '12 at 09:22
  • Oh, so it is. I recognise it now - carry `ptr` into the scope (why?), take a `T*`, do nothing. The point being, presumably, that `boost` and `std` shared pointers don't know about each other's counts, so shouldn't destroy an object originally held by the other one? – Chowlett Sep 07 '12 at 09:25
  • Yes, I was all set to write a comment saying that this was horribly dangerous because you'd have two distinct shared_ptrs both responsible for deleting the same memory, then I saw the original poster had done it correctly :) – jcoder Sep 07 '12 at 09:28
  • 12
    +1 Wow, really nice trick, use capturing by-value to keep a copy of the original `ptr` (and thus an additional refcount) inside of the deleter lambda. Took me a while to figure that out, I have to admit. – Christian Rau Sep 07 '12 at 09:35
  • I can't see clearly how the inner reference is decremented when the destination (the made shared_ptr) is destroyed... aren't there **two** concurrent reference counts, one for the std the other for the boost ? – Stephane Rolland Sep 07 '12 at 12:09
  • When the destination is destroyed then its deleter (which holds a reference to the source) is also destroyed, thus decrementing the reference of the source. What about two concurrent reference counts, the life-time of the pointer is still handled only by the source. – ronag Sep 07 '12 at 12:14
  • the reference is kept by the parameter of the lambda so as to be used (late after) in the deleter, isn't it ? – Stephane Rolland Sep 07 '12 at 12:26
  • and the lambda is destroyed at the same time ? – Stephane Rolland Sep 07 '12 at 12:38
  • 5
    @StephaneRolland The lambda deleter is stored as a member of the `boost::shared_ptr` and it in turn has a copy of the `std::shared_ptr` as member (due to the by-value lambda capture), which increments the reference count for the original object. So each `boost::shared_ptr` increases the reference count of the original object through its deleter member. But when the reference count of the `boost::shared_ptr` (which is indeed independent of the `std` one's) reaches 0, nothing is deleted, since the deleter is a no-op, but the destruction of the deleter actually decrements the original refcount. – Christian Rau Sep 07 '12 at 13:03
  • @ChristianRau: Just one point, in some (most?) implementation the deleter is stored together with the reference counter (i.e. shared by all instances), thus the source reference count is only incremented once, during the construction of the deleter lambda. – ronag Sep 07 '12 at 13:29
  • @ronag Ah, I wasn't sure about that, but fortunately it doesn't change the correctness of your solution (and should actually be good for the performance). Thanks for the info, though. – Christian Rau Sep 07 '12 at 13:46
  • just to be sure to understand: when is the lambda object destroyed ? – Stephane Rolland Sep 07 '12 at 23:23
  • 1
    @StephaneRolland: When the reference count of the destination reaches 0. You could also take look at the code for `std::shared_ptr` or `boost::shared_ptr` and find out. – ronag Sep 08 '12 at 08:10
  • yeah you're right, but often I'm still afraid of reading the code of STL... but you're right I should start to do that. – Stephane Rolland Sep 08 '12 at 13:11
  • Very clever, but there's a pitfall. If you call `make_shared_ptr` on the `std::shared_ptr` returned from `make_shared_ptr`, you create *two* `shared_ptr`'s that hold references to each other, thus creating a cyclic reference. Those objects will never be deleted since they keep each other alive. – Eric Niebler Sep 08 '12 at 15:06
  • @EricNiebler: Actually there is no problem. I believe you are incorrect, i.e. there is no cyclic reference. Try it yourself... – ronag Sep 08 '12 at 15:16
  • In order to avoid memory leaks you should create a named temporary for the boost::shared_ptr. This is also mentioned in the "Best Practises" [http://www.boost.org/doc/libs/1_51_0/libs/smart_ptr/shared_ptr.htm#BestPractices] – janr Oct 08 '12 at 07:39
  • janr: I don't think that is relevant in this case. – ronag Oct 08 '12 at 08:07
  • 6
    The approach is interesting if flawed. The deleter is held together with the count and executed when all *strong* references go out of scope. The problem with this approach is that in the presence of a `weak_ptr` the deleter will be called, and nothing will happen, and while there is at least that one `weak_ptr`, the lifetime of the object will be artificially extended by the reference inside the deleter, breaking the semantics of the combination of `weak_ptr`/`shared_ptr`combination. The fix is simple, the design is good, just the implementation needs to be changed: `[ptr](T*){ptr.reset();}` – David Rodríguez - dribeas Oct 13 '13 at 20:33
  • @DavidRodríguez-dribeas: Indeed, good catch! I'll update the answer. – ronag Oct 13 '13 at 21:05
  • 2
    @ronag Doesn't the lambda have to be `mutable` now? (Yes, [just confirmed](http://coliru.stacked-crooked.com/a/cd36aba0db83d79c).) – dyp Oct 13 '13 at 21:31
  • Doesn't this have the problem of hijacking the lifetime of the source `shared_ptr`? In other words, wouldn't the source `shared_ptr` have it's refcount set to zero when the destination `shared_ptr` is released? – Ben Collins Jan 06 '14 at 06:01
  • @ronag ah, I didn't quite think through that the capture was by value. – Ben Collins Jan 06 '14 at 18:26
  • 2
    @DavidRodríguez-dribeas: I believe there is still a flaw with the presence of a `weak_ptr` even with your correction. If, for example, you create a `std shared_ptr`, then make a `boost::shared_ptr` off that (using above method), then create a `boost::weak_ptr` off the `boost::shared_ptr`, when the `boost::shared_ptr` is deleted the `weak_ptr` will think the the object no longer exists (`weak_ptr.lock()` will return `null`) even though the actual object (ie the original `std::shared_ptr`) still exists. – Arieh Nov 28 '14 at 03:36
  • @Arieh: Yep, he changed belated release in the face of weak references to premature dissociation in the face of strong pointers below that level. The problem is fundamentally unsolvable unless internals are known and the direct members of a `shared_ptr` are at most switched. – Deduplicator Nov 19 '15 at 20:47
  • @Arieh: Added a note. – ronag Nov 19 '15 at 21:24
  • 1
    There's another flaw with this answer: if you use it twice (to convert and then convert back) on the same pointer you will create a reference cycle (ie. a memory leak). This is solved better in [this answer](http://stackoverflow.com/a/12605002/43534), although with the same caveat that converted weak_ptrs might expire early. (I don't think that's readily solvable.) – Miral Mar 03 '17 at 05:21
  • @Miral: You won't get a reference cycle. But the answer you linked to has an interesting optimization if you are converting back and forth a lot. – ronag Mar 03 '17 at 19:47