返回 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
是不可复制的。
之所以有效,是因为您将左值 a
转换为右值,并且 return 值可以移动构造。
和 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()))
{ }
我有一个基础 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
是不可复制的。
之所以有效,是因为您将左值
a
转换为右值,并且 return 值可以移动构造。和 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()))
{ }