Does C++11 unique_ptr and shared_ptr able to convert to each other's type?
Asked Answered
G

5

159

Does C++11 standard library provide any utility to convert from a std::shared_ptr to std::unique_ptr, or vice versa? Is this safe operation?

Germaun answered 17/6, 2016 at 14:58 Comment(4)
Define "safe operation" please. What sort of safety are you looking for? lifetime management safety? Thread safety?Queensland
"STL" doesn't mean standard library. The STL has nothing to do with shared_ptr.Stibnite
@Queensland Thread safety would mean you have owners used in different threads, i.e. the use count is not 1.Stibnite
@Stibnite I knew that. My point was "safety" was not well defined in OP's question, and he needed to clarify which kind of "safety" he meant as there are multiple kinds.Queensland
B
259

std::unique_ptr is the C++11 way to express exclusive ownership, but one of its most attractive features is that it easily and efficiently converts to a std::shared_ptr.

This is a key part of why std::unique_ptr is so well suited as a factory function return type. Factory functions can’t know whether callers will want to use exclusive ownership semantics for the object they return or whether shared ownership (i.e., std::shared_ptr) would be more appropriate. By returning a std::unique_ptr, factories provide callers with the most efficient smart pointer, but they don’t hinder callers from replacing it with its more flexible sibling.

std::shared_ptr to std::unique_ptr is not allowed. Once you’ve turned lifetime management of a resource over to a std::shared_ptr, there’s no changing your mind. Even if the reference count is one, you can’t reclaim ownership of the resource in order to, say, have a std::unique_ptr manage it.

Reference: Effective Modern C++. 42 SPECIFIC WAYS TO IMPROVE YOUR USE OF C++11 AND C++14. Scott Meyers.

In short, you can easily and efficiently convert a std::unique_ptr to std::shared_ptr but you cannot convert std::shared_ptr to std::unique_ptr.

For example:

std::unique_ptr<std::string> unique = std::make_unique<std::string>("test");
std::shared_ptr<std::string> shared = std::move(unique);

or:

std::shared_ptr<std::string> shared = std::make_unique<std::string>("test");
Blouse answered 17/6, 2016 at 15:25 Comment(4)
Be aware that while not allowed the compiler (at least not gcc) will actually not prevent (or even warn) if you accidentally (e.g. by changing the pointer type of a member variable) assign a std::unique_ptr to a std::shared_ptr.Issiah
STL reference: en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptrAnastasio
@Issiah What makes you think that it's not allowed to assign a std::unique_ptr into a std::shared_ptr? The standard library defines a move assignment operator template<class Y, class Deleter> shared_ptr& operator=(std::unique_ptr<Y, Deleter>&& r); for std::shared_ptr<T>.Wavell
I want to highlight that the conversion from unique_ptr to shared_ptr works because shared_ptr class has knowledge of unique_ptr and it is equipped with both constructor and assignment operator that accept a rvalue reference to unique_ptr (the rvalue reference is essential because guarantees the unique_ptr is not going to be used anymore). It is not std::move that makes the magic, but shared_ptr class.Broadfaced
D
2

I would prefer to do this.

std::unique_ptr<int> up_ = std::make_unique<int>();
std::shared_ptr<int> sp_ = std::move(up_);
Daybook answered 6/8, 2021 at 16:24 Comment(0)
I
1

While converting from std::shared_ptr<T> to std::unique_ptr<T> is by design impossible (since it would allow std::unique_ptr<T>'s single-reference invariant to be violated), it's still possible to move the contents of a shared object into a new instance managed by a unique pointer. For example:

std::shared_ptr<std::string> shared = std::make_shared<std::string>("test");
std::unique_ptr<std::string> unique = std::make_unique<std::string>(std::move(*shared));

In the second line above, the contents of *shared (i.e. the string managed by the shared pointer) are moved into a new std::string instance managed by the unique pointer. The side-effect of this is that the std::string object managed by shared is left in a "valid but unspecified state", since it has been stripped of its contents. But since the whole point of making a managed object "unique" is to prevent it from being accessed from anywhere else, I would say this is actually in keeping with the spirit of the operation.

Interrogate answered 26/9, 2023 at 17:13 Comment(0)
T
0

You can assign your shared-pointer to a unique-pointer.

A custom deleter may be provided, which allows ownership to be transferred.

While the custom deleter usually transfers ownership to the thing that will delete the pointer, the pointer may also be sent somewhere (anywhere) else. Using the custom deleter enforces the already obvious requirement that ownership only be transferred when the shared pointer itself is going out of scope.

Here is a minimal example:

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> up;
    
    {
        std::shared_ptr<int> sp = std::shared_ptr<int>(new int (42),
            [&up] (int* ptr) {up = std::unique_ptr<int>(ptr); });
    }
    
    std::cout << "Unique-ptr = " << *up << "\n";
}

Disclaimer: I make no comments as to whether or not this is a terrible idea!

Transvestite answered 29/9, 2023 at 14:41 Comment(0)
C
-10

Given unique_ptr u_ptr, create shared_ptr s_ptr:

std::shared_ptr<whatever> s_ptr(u_ptr.release());

Going the other way is impractical.

(As many people have noted, there is more to this story, and it's worth reading the comments.)

Captious answered 16/12, 2016 at 0:39 Comment(13)
Here's the "right" way: std::shared_ptr<whatever> s_ptr(std::move(u_ptr));Saltzman
And here is the pedantic "right" way: std::shared_ptr<whatever> s_ptr{std::move(u_ptr)};Saturn
@polyvertex: is there any actual difference between these two syntaxes in this particular case?Bluey
@nmr's conversion is not wrong, just a bit not safer, so don't downvote his answer. I consider emlai's conversion the "right" way.Arluene
What's less safe about it?Captious
@VioletGiraffe • I suppose polyvertex is advocating using the new initialization list syntax — which avoids silent narrowing conversions and accommodates member initialization with a uniform syntax — as a good habit to get into. Six of one, half-dozen of the other?Waltz
Initialization in C++ is a big fat mess, and it's finally getting fixed in C++20. We should all use the new uniform way. See: youtube.com/watch?v=9-_TLTdLGtcBattleship
@Captious It is unsafe because you may lose the Deleter stored inside the unique_ptrKenleigh
Would be the only pre C++11 way though, wouldn't it be?Jeniferjeniffer
@JimmyR.T. shared_ptr and unique_ptr were introduced with C++11.Linnealinnean
@ZangMingJie Why "may lose" and not just "lose" ? I see no reason for compilers to recognize the intent and forward the deleter to the shared pointer in that situation. IMHO the custom deleter gets simply lost with nmr's solution, which is why the answer is not just "a bit not safer", but rather "dangerous"Gerfen
After reading the comments I believe we can fix the answer with: std::shared_ptr<whatever> s_ptr(u_ptr.release(), u_ptr.get_deleter());Broadfaced
@Captious this answer is very useful, if nothing else for the interesting discussion in the comments. I will upvote it, maybe it would be useful to add a note in the actual answer regarding the deleter?Broadfaced

© 2022 - 2025 — McMap. All rights reserved.