如何将std::unique_ptr传递给函数?

167

如何将std::unique_ptr传递给一个函数?假设我有以下类:

class A
{
public:
    A(int val)
    {
        _val = val;
    }

    int GetVal() { return _val; }
private:
    int _val;
};

以下代码无法编译:

void MyFunc(unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(ptr);

    return 0;
}

为什么我不能把std::unique_ptr传递给函数?这难道不是该构造的主要目的吗?或者C++委员会是打算让我返回原始的C风格指针并像这样传递吗:

MyFunc(&(*ptr)); 

而最奇怪的是,为什么这种方式可以通过?它看起来非常不一致:

MyFunc(unique_ptr<A>(new A(1234)));

10
只要使用非拥有型的原始C指针,就可以放心地使用“回归”原始C指针。您可能更喜欢编写“ptr.get()”。但是,如果您不需要空值,则最好使用引用。 - Chris Drew
1
@mosegui 这是一个6年前的问题,已经有答案了。 - user3690202
8个回答

233

这里基本上有两个选项:

通过引用传递智能指针

void MyFunc(unique_ptr<A> & arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(ptr);
}

将智能指针移入函数参数中

请注意,在这种情况下,断言将成立!

void MyFunc(unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(move(ptr));
    assert(ptr == nullptr)
}

2
谢谢。在这种情况下,我想创建一个实例,在其上执行一些操作,然后将所有权移交给其他人,这时使用move()似乎是完美的选择。 - user3690202
9
如果函数可能移动unique_ptr,则应该仅通过引用传递,并且应该是右值引用。如果想要观察对象而不需要关心其所有权语义,则使用像A const&A&这样的引用。 - Potatoswatter
32
请听Herb Sutter在CppCon 2014上的演讲。他强烈反对将引用传递给unique_ptr<>。其要点是,当你处理所有权时,你应该只使用像unique_ptr<>或shared_ptr<>这样的智能指针。请访问www.youtube.com/watch?v=xnqTKD8uD64以查看演讲视频,您还可以在https://github.com/CppCon/CppCon2014/tree/master/Presentations/Back%20to%20the%20Basics!%20Essentials%20of%20Modern%20C%2B%2B%20Style找到幻灯片。 - schorsch_76
4
这只是一种展示移动后ptr值的方式。 - Bill Lynch
1
@bloody 是的。只观察 A 的函数不需要了解其所有权,所以智能指针是一个外部关注点。 - Potatoswatter
显示剩余6条评论

57
你正在传递它的值,这意味着制作一份副本。那就不是很独特了,对吧?
你可以移动该值,但这意味着将对象的所有权和生命周期控制传递给函数。
如果对象的生命周期保证存在于对MyFunc的调用期间,请通过ptr.get()传递一个原始指针。

传递 unique_ptr 引用的开销是否足以证明需要调用 ptr.get() 并暴露原始指针呢? - User 10482
1
@User10482请看问题本身的评论:“只要它们是非拥有的原始指针,回退到原始C风格指针没有任何问题。您可能更喜欢编写'ptr.get()'。但是,如果您不需要可空性,则应优先使用引用。”以及在被接受的答案中关于Herb Sutter不鼓励传递unique_ptr引用的评论。 - Mark Tolonen
实际上,按值传递unique_ptr并不意味着复制,这是最好的方法。请考虑unique_ptr只有移动构造函数,因此调用者必须使用“move”函数或直接创建unique_ptr。 这可以作为一个很好的参考: http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rr-uniqueptrparam - Triskeldeian
2
@Triskeldeian 这就是问题的关键。以OP所做的方式通过值传递unique_ptr 确实意味着一份副本,但由于没有复制构造函数,显然不起作用。我确实提到他们可以移动该值,但那会传递所有权。 - Mark Tolonen

24

为什么我不能将 unique_ptr 传递给函数?

这是因为 unique_ptr 具有移动构造函数但没有复制构造函数。根据标准,当定义了移动构造函数但未定义复制构造函数时,复制构造函数会被删除。

12.8 复制和移动类对象

...

7 如果类定义没有明确声明复制构造函数,则会隐式声明一个。如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的复制构造函数将被定义为已删除;

您可以使用以下方式将 unique_ptr 传递给函数:

void MyFunc(std::unique_ptr<A>& arg)
{
    cout << arg->GetVal() << endl;
}

并像您现在所做的那样使用它:

或者

void MyFunc(std::unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

并像这样使用:

std::unique_ptr<A> ptr = std::unique_ptr<A>(new A(1234));
MyFunc(std::move(ptr));

重要提示

请注意,如果您使用第二种方法,ptr 在调用 std::move(ptr) 后不再拥有指针的所有权。

void MyFunc(std::unique_ptr<A>&& arg)void MyFunc(std::unique_ptr<A>& arg) 将产生同样的效果,因为它们都是引用。

在第一种情况下,调用 MyFunc 后,ptr 仍然拥有指针的所有权。


7
作为 MyFunc 不拥有所有权,最好这样做:
void MyFunc(const A* arg)
{
    assert(arg != nullptr); // or throw ?
    cout << arg->GetVal() << endl;
}

更好的。
void MyFunc(const A& arg)
{
    cout << arg.GetVal() << endl;
}

如果你真的想要拥有,你就必须移动你的资源:

std::unique_ptr<A> ptr = std::make_unique<A>(1234);
MyFunc(std::move(ptr));

或者直接传递一个右值引用:

MyFunc(std::make_unique<A>(1234));

std::unique_ptr没有复制的目的是为了保证只有一个所有者。


1
这个答案缺少你前两种情况下对MyFunc调用的代码。不确定你是使用ptr.get()还是直接将ptr传递到函数中? - taylorstine
MyFunc 的预期签名是什么,以传递一个右值引用? - James Hirschorn
@JamesHirschorn: void MyFunc(A&& arg) 接受右值引用... - Jarod42
@Jarod42 我在 ideone 上确认了 C++14 的失败:https://ideone.com/YRRT24 - James Hirschorn
@JamesHirschorn:你的问题是要创建int (&&)[]。你可以提出一个专门的问题来解决它。 - Jarod42
显示剩余2条评论

6
为什么我不能将unique_ptr传递给函数? 您可以,但不能通过复制 - 因为std :: unique_ptr<>不可复制。 这当然是构造的主要目的吗? 除其他外,std :: unique_ptr<>旨在明确标记唯一所有权(而不是std :: shared_ptr<>)。 最奇怪的是,为什么这是传递它的一种好方法? 因为在这种情况下,没有复制构造。

谢谢你的回答。只是出于兴趣,你能解释一下为什么最后一种传递方式没有使用复制构造函数吗?我本以为使用unique_ptr的构造函数会在堆栈上生成一个实例,然后使用复制构造函数将其复制到MyFunc()的参数中?虽然我承认我对这个领域的记忆有点模糊。 - user3690202
2
由于这是一个rvalue,因此将调用移动构造函数。尽管您的编译器肯定会将其优化掉。 - Nielk

1

由于unique_ptr用于独占所有权,如果您想将其作为参数传递,请尝试

MyFunc(move(ptr));

但是在此之后,main 函数中的 ptr 状态将会变为 nullptr

6
“will be undefined” - 不,它将变为null,否则unique_ptr就会变得毫无用处。 - T.C.

0
为了借鉴现有的答案,C++智能指针与所有权的概念密切相关。你的代码应该清楚地表达你将所有权传递给另一个组件(如函数、线程等)的意图,这就是为什么有独占指针、共享指针和弱指针。独占指针已经暗示了在某一时刻只有一个组件拥有该指针的所有权,因此独占所有权不能被共享,只能被移动。也就是说,当前拥有指针的所有者将在离开上下文时按自己的意愿销毁它,通常是在块的末尾或析构函数中。将对象传递给另一个函数可能意味着共享或移动所有权。如果你确定在通过引用调用函数时,所有者不会离开上下文,那么可以通过引用调用,但这样做只是丑陋的。它增加了一些前置条件和后置条件,并且会增加代码的不可移植性,同时也破坏了指针的智能性,使其变成了一个更原始的指针。例如,将“函数”更改为在另一个线程中启动和执行自己的任务就不再可能。虽然在代码的某些非常受限制的部分仍然可以选择通过引用传递,比如为了避免在独占指针和共享指针之间来回移动产生额外开销,但我强烈建议避免这样做,特别是在公共接口中。

-1
std::unique_ptr<T>作为值传递给函数是不起作用的,因为正如你们所提到的,unique_ptr是不可复制的。
这个怎么样?
std::unique_ptr<T> getSomething()
{
   auto ptr = std::make_unique<T>();
   return ptr;
}

这段代码可以运行


如果那段代码能够正常工作,那么我的猜测是它并没有复制 unique_ptr,而是使用了移动语义来移动它。 - user3690202
1
可能是返回值优化(RVO),我猜。 - Noueman Khalikine

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