Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 670
stefan Avatar asked Sep 19 '13 08:09

stefan


People also ask

Can you convert a shared_ptr to unique_ptr?

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.

Should I use unique_ptr or shared_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.

Can I dereference a unique_ptr?

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().

Can unique_ptr be copied?

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.


2 Answers

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.

like image 96
Ali Avatar answered Oct 03 '22 00:10

Ali


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.

like image 36
Angew is no longer proud of SO Avatar answered Oct 03 '22 01:10

Angew is no longer proud of SO