creating a shared_ptr from unique_ptr

C++C++11G++Smart PointersClang++

C++ Problem Overview


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

C++ Solutions


Solution 1 - C++

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.

Solution 2 - C++

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 ints, 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.

Solution 3 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionstefanView Question on Stackoverflow
Solution 1 - C++AliView Answer on Stackoverflow
Solution 2 - C++Angew is no longer proud of SOView Answer on Stackoverflow
Solution 3 - C++Jonathan WakelyView Answer on Stackoverflow