返回标准::make_unique如何<SubClass>工作?

How does returning std::make_unique<SubClass> work?

本文关键字:SubClass 工作 lt gt 如何 标准 make unique 返回      更新时间:2023-10-16

有一个基类和它的子类:

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

尝试创建一个辅助函数来创建派生对象的唯一指针。

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

2)但是,这个编译失败:

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

std::unique_ptr<Base> GetDerived3() {
    auto a = std::make_unique<Derived>();
    return std::move(a); 
}
4)如果我创建一个Base实例,两者都可以工作:
std::unique_ptr<Base> GetDerived4() {
    auto a = std::make_unique<Base>();
    return a; 
}
std::unique_ptr<Base> GetDerived5() {
    auto a = std::make_unique<Base>();
    return std::move(a); 
}

为什么(2)失败了,而其他人成功了?

std::unique_ptr不可复制,只能移动。您可以从声明返回std::unique_ptr<Base>的函数中获取return std::make_unique<Derived>的原因是存在从一个到另一个的转换。

所以1)等价于:

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

由于std::make_unique返回的值是右值,因此返回值是移动构造的。

与2)对比,相当于:

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

由于a是左值,返回值必须是复制构造的,而std::unique_ptr是不可复制的。

  • 可以工作,因为您将左值a强制转换为右值,并且返回值可以移动构造。

  • 和5)工作,因为你已经有一个std::unique_ptr<Base>,不需要构造一个返回。

  • 在除(2)之外的所有情况下,返回值都被视为(某种)右值。在(2)中不是,因为类型不匹配,隐式移动被阻塞。

    在标准的后续迭代中,(2)也将隐式移动。

    当它们试图通过指向- Base的指针删除Derived对象时,它们在被调用后很快就会发生未定义的行为。要解决这个问题,请记录一个delete函数。

    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*){} };
    

    这些是唯一的指针,足够智能,可以像shared_ptr一样处理多态性。

    std::unique_ptr<>没有复制构造函数,但它有一个从相关指针移动的构造函数,即

    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
    

    第二个构造函数需要某些条件(参见这里)。那么为什么代码2不能正常工作,而代码4却可以呢?在第4例中,您没有使用任何构造函数,因为返回类型与对象相同,因此返回对象本身。另一方面,在2中,返回类型不同,需要调用构造函数,但这需要std::move()

    在上面列出的示例中,(1)返回右值,但(2)不是右值,并且试图在unique_ptr上复制,这对于unique_ptr来说是无法做到的。

    使用move是有效的,因为您将该点的unique_ptr作为右值处理。

    如果查看std::unique_ptr的定义,您会发现std::unique_ptr可以从派生类型移动到基类型。基本上is_convertible在这里检查这种情况。

       /** @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()))
    { }