将 unique_ptr<Base> 向下转型为 unique_ptr<Derived>

72

我有一系列的工厂会返回unique_ptr<Base>。但实际上,在内部它们提供了指向各种派生类型(unique_ptr<Derived>, unique_ptr<DerivedA>, unique_ptr<DerivedB>等)的指针。

给定DerivedA : DerivedDerived : Base,我们有:

unique_ptr<Base> DerivedAFactory() {
    return unique_ptr<Base>(new DerivedA);
}

我需要做的是将从返回的unique_ptr<Base>中的指针“转换”为某个派生层次(不一定是原始内部层次)。举个伪代码的例子:

unique_ptr<Derived> ptr = static_cast<unique_ptr<Derived>>(DerivedAFactory());

我打算通过释放 unique_ptr 中的对象,然后使用一个将裸指针转换并重新分配给另一个所需类型 unique_ptr 的函数来完成此操作(在调用之前,release会由调用者明确执行):

unique_ptr<Derived> CastToDerived(Base* obj) {
    return unique_ptr<Derived>(static_cast<Derived*>(obj));
}

这个是否有效,或者会出现奇怪的事情发生?


PS. 增加了一个复杂性,在某些工厂中驻留在动态加载的DLL中,这意味着我需要确保生成的对象在与它们创建时相同的上下文(堆空间)中被销毁。所有权的转移(通常发生在另一个上下文中)必须从原始上下文提供删除器。除了必须提供/强制转换指针的删除器之外,转换问题应该是相同的。


1
我会让 CastToDerived 使用 unique_ptr<T>&&。之所以没有与 shared_ptrstatic_pointer_cast 等效的转换,是因为通常转换不会修改其参数。但对于 unique_ptr,你必须将指针从参数移动到转换返回的对象中。 - dyp
@dyp 我认为在这种情况下,swap 也可能是一个不错的选择。 - user2485710
@d7samurai(继续..)你的CastToDerived函数可以通过CastToDerived(my_ptr.get())(这是一个错误)和CastToDerived(my_ptr.release())(这是正确的)来调用。为了防止前者,我建议使用像CastToDerived(std::move(my_ptr))这样显式的方式,可能会更加明确,也不容易出错。或者,在名称上进行明确,比如move_static_cast<Derived>(my_ptr) - dyp
是的,它的草图表明调用者执行my_ptr.release()。这样做的原因当然是因为调用者必须意识到释放。 - d7samurai
@d7samurai 是的,但在我看来,这是容易出错的,因为调用者可能会直接使用 my_ptr.get()。因此,在调用站点强制使用 std::move,或让转换函数移动指针,但这样做需要让转换函数的名称表明它会修改其参数。 - dyp
显示剩余2条评论
1个回答

50

我会创建一对函数模板static_unique_ptr_castdynamic_unique_ptr_cast。在您绝对确定指针实际上是Derived *的情况下,请使用前者,否则请使用后者。

template<typename Derived, typename Base, typename Del>
std::unique_ptr<Derived, Del> 
static_unique_ptr_cast( std::unique_ptr<Base, Del>&& p )
{
    auto d = static_cast<Derived *>(p.release());
    return std::unique_ptr<Derived, Del>(d, std::move(p.get_deleter()));
}

template<typename Derived, typename Base, typename Del>
std::unique_ptr<Derived, Del> 
dynamic_unique_ptr_cast( std::unique_ptr<Base, Del>&& p )
{
    if(Derived *result = dynamic_cast<Derived *>(p.get())) {
        p.release();
        return std::unique_ptr<Derived, Del>(result, std::move(p.get_deleter()));
    }
    return std::unique_ptr<Derived, Del>(nullptr, p.get_deleter());
}

使用右值引用作为函数参数,以确保您不会通过将传递给您的unique_ptr“窃取”而使调用者无法正常运行。


6
您需要从源对象中提取删除器并将其注入目标对象中。类型可能不足以支持此操作。 - David Rodríguez - dribeas
1
@Praetorian [unique.ptr.single.asgn] 的例子中说:“效果: 将所有权从 u 转移到 *this,就像调用 reset(u.release()) 然后从 std::forward<D>(u.get_deleter()) 进行赋值一样。” 所以我认为它是明确定义的,尽管删除器不出现在 release 的后置条件中。 - dyp
1
顺便提一下,删除器可以是仅限移动的,这使得最终的返回语句有点困难(你不能复制,也不应该移动,也许你不能默认构造)。如果它是仅限移动的,也许你可以要求 DefaultConstructible;另一种方法是抛出异常而不是返回。 - dyp
5
你好,@Praetorian。我一直在尝试使用这个代码并且在使用自定义删除器时能够使其正常工作 (http://pastebin.com/EABRT9G3)。但是,在使用默认删除器时(http://pastebin.com/BNDBivff),我不确定该如何做- 在这种情况下,编译器会抱怨派生类/基类删除器之间没有可行的转换方法。是否可以使用默认删除器?如果可以,请查看我的第二个 pastebin,并让我知道我需要如何使用正确的模板参数调用 cast 函数。谢谢 :) - georgemp
2
@Malvineous unique_ptr 的构造函数是 noexcept 的。但是你建议的先构造第二个 unique_ptr,然后释放原始指针的所有权,如果复制/移动删除器可能会抛出异常,则会更加可靠。 - Praetorian
显示剩余26条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接