有一个基类和它的子类:
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()))
{ }