creating a shared_ptr from unique_ptr
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:
- g++-4.4.7 gives compilation error
- g++-4.6.4 compiles without any error
- g++-4.7.3 gives internal compiler error
- g++-4.8.1 gives compilation error
- clang++-3.2.1 compiles without any error
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
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.
Here's a reduced example which clang incorrectly compiles:
struct ptr
{
int* p;
explicit operator bool() const { return p != nullptr; }
};
int main()
{
ptr u{};
int* p = new int(u);
}
Clang uses the explicit bool conversion operator to initialize the int
(and the Intel compiler does too.)
Clang 3.4 does not allow:
int i = int(u);
but it does allow:
int* p = new int(u);
I think both should be rejected. (Clang 3.3 and ICC allow both.)
I've added this example to the bug report.