creating a shared_ptr from unique_ptr

stefan picture stefan · Sep 19, 2013 · Viewed 42.9k times · Source

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

Answer

Ali picture Ali · Sep 19, 2013

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.