creating a shared_ptr from unique_ptr

后端 未结 3 844
北荒
北荒 2021-02-04 23:17

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_

相关标签:
3条回答
  • 2021-02-05 00:03

    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.

    0 讨论(0)
  • 2021-02-05 00:07

    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.

    0 讨论(0)
  • 2021-02-05 00:09

    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.

    0 讨论(0)
提交回复
热议问题