将指向派生类的unique_ptr作为参数传递给接受基类unique_ptr的函数

106

我正在尝试在一个接受基类unique_ptr的函数中使用指向派生类的unique_ptr。就像这样:

class Base {};

class Derived : public Base {};

void f(unique_ptr<Base> const &base) {}

…

unique_ptr<Derived> derived = unique_ptr<Derived>(new Derived);
f(derived);

如果我正确理解这个答案,那么这段代码应该可以工作,但它会导致以下编译错误:

error C2664: 'f' : cannot convert parameter 1 from 'std::unique_ptr<_Ty>' to 'const std::unique_ptr<_Ty> &'

IntelliSense:不存在适当的用户定义的转换,从“std::unique_ptr<Derived,std::default_delete<Derived>>”到“const std::unique_ptr<Base,std::default_delete<Base>>”

如果我将f更改为接受unique_ptr<Derived> const &derived,那么它可以正常工作,但那不是我想要的。

我做错了什么吗?我能做些什么来解决这个问题?

我正在使用Visual Studio 2012。

4个回答

109

你有三个选项:

  1. 放弃所有权。这将使你的本地变量在函数调用后无法访问动态对象;对象已被传递给被调用方:

f(std::move(derived));
更改 f 的签名:
void f(std::unique_ptr<Derived> const &);
  • 更改您的变量类型:

    std::unique_ptr<base> derived = std::unique_ptr<Derived>(new Derived);
    

    或者当然也可以只是:

    std::unique_ptr<base> derived(new Derived);
    

    或者甚至:

    std::unique_ptr<base> derived = std::make_unique<Derived>();
    
  • 更新:或者,如评论中建议的那样,根本不要转让所有权:

  • void f(Base & b);
    
    f(*derived);
    

    2
    在这种情况下,我考虑使用4。使用shared_ptr - svick
    4
    使用参考(reference)而不是 unique_ptr 作为函数调用的参数,如何? - ltjax
    23
    如果函数不接管指针,为什么要传递一个智能指针呢?这不是智能指针的用途。 - Jonathan Wakely
    2
    @metal:太好了,谢谢 - 是的,很重要的细节,返回类型必须匹配。我错过了那个。不错。你本可以说return std::unique_ptr<Base>(std::move(p)),我猜。 - Kerrek SB
    1
    “这将在函数调用结束时销毁对象。”但是我们不知道 f() 在做什么(假设它实际上不是空的)- 它可能会将所有权传递给其他地方。我们能说的只是我们放弃或转移所有权吗? - Zitrax
    显示剩余5条评论

    53

    我尝试了被接受答案的选项#1,但是我仍然遇到了相同的编译错误。我在墙上撞了一个多小时后终于意识到我

    class Derived : Base {};
    

    取而代之

    class Derived : public Base {};
    

    14
    一种可能的解决方法是将参数类型更改为Base const*,并传递derived.get()。使用unique_ptr const<Base>&不会转移所有权(也不修改unique_ptr),因此更改为Base const*不会改变意义。
    Herb Sutter在Smart Pointer Parameters中详细讨论了传递智能指针参数的问题。链接文章中的摘录提到了这种情况:

    传递一个const unique_ptr<widget>&很奇怪,因为它只能接受null或一个widget,而这个widget的生命周期通过unique_ptr在调用代码中进行管理,调用方通常不应该关心调用者的生命周期管理选择。传递widget*覆盖了这些情况的严格超集,并且可以接受“nullwidget”,而不管调用方使用的生命周期策略如何。


    1
    “Base const*” 不意味着函数可能会在某个地方存储指针吗?我认为智能指针正试图避免这种情况。 - svick
    @svick,该函数同样可以轻松存储base.get()get()是一个const成员函数)。 - hmjd
    2
    @svick 不是的。智能指针的作用是通过明确对象的所有者来避免内存泄漏。如果您将原始指针作为参数传递给函数,那么只是提供了读取/修改它的访问权限。智能指针的作用是避免使用原始的 newdelete,而不是避免使用指针。 - etam1024

    -2

    另一种方法是改变f的签名,并以稍微不同的方式使用它:

    void f(Base* base_ptr) {
        // take ownership inside the function
        std::unique_ptr<Base> base {base_ptr};
        // ...
    }
    
    // ...
    auto derived = std::make_unique<Derived>();
    f(derived.release());  // release ownership
    

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