返回 std::make_unique<SubClass> 是如何工作的?

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

我有一个基础 class 及其子class:

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

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

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

1) 这个有效:

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

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

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

3) std::move 作品:

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) 失败但其他人工作?

在上面列出的示例中,(1) returns 是一个右值,但 (2) 不是右值并且正在尝试在 unique_ptr 上进行复制,而对于 unique_ptr.

使用 move 是可行的,因为您当时将 unique_ptr 视为右值。

std::unique_ptr不可复制,只能移动。您可以从声明为 return 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 编辑的值 return 是右值,因此 return 值是移动构造的。

将其与 2) 进行对比,后者相当于:

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

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

  1. 之所以有效,是因为您将左值 a 转换为右值,并且 return 值可以移动构造。

  2. 和 5) 工作,因为你已经有了一个 std::unique_ptr<Base>,不需要构造一个到 return.

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

第二个构造函数需要特定条件(参见 here)。那么,为什么您的代码 2 不起作用,而 4 却起作用了?在 4 中,您没有使用任何构造函数,因为 return 类型与对象相同,对象本身是 returned。另一方面,在 2 中,return 类型不同,需要调用构造函数,但这需要 std::move().

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

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

它们在被调用后不久都发生了未定义的行为,因为它们试图通过指向 Base 的指针删除一个 Derived 对象。为了解决这个问题,记录一个删除函数。

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 可以从派生的移动到基的。基本上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()))
{ }