How does returning std::make_unique work?

后端 未结 5 2373
旧巷少年郎
旧巷少年郎 2021-02-19 16:05

I have a base class and its subclass:

class Base {
    public:
    virtual void hi() {
        cout << \"hi\" << endl;
    } 
};

class Derived : pub         


        
相关标签:
5条回答
  • 2021-02-19 16:55

    In every case but (2) the returned value was treated as (some kind of) rvalue. In (2) it was not, because the types did not match the implicit move was blocked.

    In a later iteration of the standard, (2) would also implicitly move.

    All of them shortly engage in undefined behaviour after being called, as they try to delete a Derived object via pointer-to-Base. To fix this, record a deleter function.

    template<class T>
    using smart_unique=std::unique_ptr<T, void(*)(void*)>;
    
    template<class T, class...Args>
    smart_unique<T> make_smart_unique( Args&&... args ){
      return {
        new T(std::forward<Args>(args)...),
        [](void*ptr){ delete static_cast<T*>(ptr); }
      };
    }
    template<class T>
    static const smart_unique<T> empty_smart_unique{ nullptr, [](void*){} };
    

    These are unique pointers smart enough to handle polymorphism like shared_ptr can.

    0 讨论(0)
  • 2021-02-19 16:58

    In the above listed example, (1) returns an rvalue but (2) is not an rvalue and is attempting a copy on the unique_ptr, which you cannot do for a unique_ptr.

    Using move works because you are treating the unique_ptr at that point as an rvalue.

    0 讨论(0)
  • 2021-02-19 16:59

    std::unique_ptr<> has no copy constructor, but it does have a move constructor from a related pointer, i.e.

    unique_ptr( unique_ptr&& u );         // move ctor
    template< class U, class E >
    unique_ptr( unique_ptr<U, E>&& u );   // move ctor from related unique_ptr
    

    The second constructor requires certain conditions (see here). So why did your code 2 not work, but 4 did? In 4, you didn't use any constructor, since the return type was identical to the object, the object itself was returned. In 2 on the other hand, the return type was different and a constructor call was needed, but that required std::move().

    0 讨论(0)
  • 2021-02-19 17:01

    std::unique_ptr is not copyable, only movable. The reason you can return std::make_unique<Derived> from a function declared to return std::unique_ptr<Base> is that there is a conversion from one to the other.

    So 1) is equivalent to:

    std::unique_ptr<Base> GetDerived() {
        return std::unique_ptr<Base>(std::made_unique<Derived>());
    }
    

    Since the value returned from std::make_unique is an rvalue, the return value is move-constructed.

    Contrast that to 2), which is equivalent to:

    std::unique_ptr<Base> GetDerived2() { 
        std::unique_ptr<Derived> a = std::make_unique<Derived>(); 
        return std::unique_ptr<Base>(a); 
    }
    

    since a is an lvalue, the return value must be copy-constructed, and std::unique_ptr is non-copyable.

    3) works because you cast the lvalue a to an rvalue, and the return value can be move-constructed.

    4) and 5) work because you already have a std::unique_ptr<Base> and don't need to construct one to return.

    0 讨论(0)
  • 2021-02-19 17:02

    You will find that std::unique_ptr can move from derived's to base's, if you look into its definition. Basically is_convertible here check this situation.

       /** @brief Converting constructor from another type
       *
       * Requires that the pointer owned by @p __u is convertible to the
       * type of pointer owned by this object, @p __u does not own an array,
       * and @p __u has a compatible deleter type.
       */
      template<typename _Up, typename _Ep, typename = _Require<
               __safe_conversion_up<_Up, _Ep>,
           typename conditional<is_reference<_Dp>::value,
                    is_same<_Ep, _Dp>,
                    is_convertible<_Ep, _Dp>>::type>>
    unique_ptr(unique_ptr<_Up, _Ep>&& __u) noexcept
    : _M_t(__u.release(), std::forward<_Ep>(__u.get_deleter()))
    { }
    
    0 讨论(0)
提交回复
热议问题