In a piece of code I reviewed lately, which compiled fine with g++-4.6
, I encountered a strange try to create a std::shared_ptr
from std::unique_ptr
:
std::unique_ptr<Foo> foo... std::make_shared<Foo>(std::move(foo));
This seems rather odd to me. This should be std::shared_ptr<Foo>(std::move(foo));
afaik, though I'm not perfectly familiar with moves (and I know std::move
is only a cast, nothing get's moved).
Checking with different compilers on this SSC(NUC*)E
#include <memory> int main() { std::unique_ptr<int> foo(new int); std::make_shared<int>(std::move(foo)); }
Results of compilation:
So the question is: which compiler is right in terms of the standard? Does the standard require this to be an invalid statement, a valid statement or is this simply undefined?
Addition
We've agreed on that some of these compilers, such as clang++ and g++-4.6.4, permit the conversion while they shouldn't. However with g++-4.7.3 (which produces an internal compiler error on std::make_shared<Foo>(std::move(foo));
), correctly rejects int bar(std::move(foo));
Because of this huge difference in behavior, I'm leaving the question as it is, although part of it would be answerable with the reduction to int bar(std::move(foo));
.
*) NUC: Not universally compilable
Afterword. The flawless conversion of an std::unique_ptr to a compatible std::shared_ptr makes it possible to write efficient and safe factory functions. However, note that an std::shared_ptr cannot be converted to an std::unique_ptr.
Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.
The unique_ptr shall not be empty (i.e., its stored pointer shall not be a null pointer) in order to be dereferenciable. This can easily be checked by casting the unique_ptr object to bool (see unique_ptr::operator bool). It is equivalent to: *get().
A unique_ptr does not share its pointer. It cannot be copied to another unique_ptr , passed by value to a function, or used in any C++ Standard Library algorithm that requires copies to be made. A unique_ptr can only be moved.
UPDATE 2: This bug has been fixed in Clang in r191150. GCC rejects the code with a proper error message.
UPDATE: I have submitted a bug report. The following code on my machine with clang++ 3.4 (trunk 191037)
#include <iostream> #include <memory> int main() { std::unique_ptr<int> u_ptr(new int(42)); std::cout << " u_ptr.get() = " << u_ptr.get() << std::endl; std::cout << "*u_ptr = " << *u_ptr << std::endl; auto s_ptr = std::make_shared<int>(std::move(u_ptr)); std::cout << "After move" << std::endl; std::cout << " u_ptr.get() = " << u_ptr.get() << std::endl; std::cout << "*u_ptr = " << *u_ptr << std::endl; std::cout << " s_ptr.get() = " << s_ptr.get() << std::endl; std::cout << "*s_ptr = " << *s_ptr << std::endl; }
prints this:
u_ptr.get() = 0x16fa010 *u_ptr = 42 After move u_ptr.get() = 0x16fa010 *u_ptr = 42 s_ptr.get() = 0x16fa048 *s_ptr = 1
As you can see, the unique_ptr
hasn't been moved from. The standard guarantees that it should be null after it has been moved from. The shared_ptr
points to a wrong value.
The weird thing is that it compiles without a warning and valgrind doesn't report any issues, no leak, no heap corruption. Weird.
The proper behavior is shown if I create s_ptr
with the shared_ptr
ctor taking an rvalue ref to a unique_ptr
instead of make_shared
:
#include <iostream> #include <memory> int main() { std::unique_ptr<int> u_ptr(new int(42)); std::cout << " u_ptr.get() = " << u_ptr.get() << std::endl; std::cout << "*u_ptr = " << *u_ptr << std::endl; std::shared_ptr<int> s_ptr{std::move(u_ptr)}; std::cout << "After move" << std::endl; std::cout << " u_ptr.get() = " << u_ptr.get() << std::endl; //std::cout << "*u_ptr = " << *u_ptr << std::endl; // <-- would give a segfault std::cout << " s_ptr.get() = " << s_ptr.get() << std::endl; std::cout << "*s_ptr = " << *s_ptr << std::endl; }
It prints:
u_ptr.get() = 0x5a06040 *u_ptr = 42 After move u_ptr.get() = 0 s_ptr.get() = 0x5a06040 *s_ptr = 42
As you see, u_ptr
is null after the move as required by the standard and s_ptr
points to the correct value. This is the correct behavior.
(The original answer.)
As Simple has pointed out: "Unless Foo has a constructor that takes a std::unique_ptr it shouldn't compile."
To expand on it a little bit: make_shared
forwards its arguments to T's constructor. If T doesn't have any ctor that could accept that unique_ptr<T>&&
it is a compile error.
However, it is easy to fix this code and get what you want (online demo):
#include <memory> using namespace std; class widget { }; int main() { unique_ptr<widget> uptr{new widget}; shared_ptr<widget> sptr(std::move(uptr)); }
The point is: make_shared
is the wrong thing to use in this situation. shared_ptr
has a ctor that accepts an unique_ptr<Y,Deleter>&&
, see (13) at the ctors of shared_ptr
.
This shouldn't compile. If we disregard the uniqueness and sharedness of the pointers for a moment, it's basically trying to do this:
int *u = new int; int *s = new int(std::move(u));
It means it's dynamically creating an int
and initialising it with an rvalue reference to std::unique_ptr<int>
. For int
s, that simply shouldn't compile.
For a general class Foo
, it depends on the class. If it has a constructor taking a std::unique_ptr<Foo>
by value, const ref or rvalue ref, it will work (but maybe not do what the author intended). In other cases, it shouldn't compile.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With