【问题标题】:Should I pass a shared_ptr by reference? [duplicate]我应该通过引用传递 shared_ptr 吗? [复制]
【发布时间】:2012-01-13 04:50:39
【问题描述】:

传递 shared_ptr 的最佳做法是什么?

目前我像这样传递 shared_ptr 函数参数:

void function1( shared_ptr<TYPE>& value );

【问题讨论】:

标签: c++ shared-ptr


【解决方案1】:

在受控情况下,您可以通过常量引用传递共享指针。确保没有人同时删除该对象,但如果您注意引用的对象,这应该不会太难。

一般来说,您应该将共享指针作为直接副本传递。这赋予了它预期的语义:每个包含共享指针副本的作用域都凭借其在所有权中的“份额”使对象保持活动状态。

不总是按值传递的唯一原因是,由于原子引用计数更新,复制共享指针需要付出一定的代价;但是,这可能不是主要问题。


可选题外话:

既然已经回答了主要问题,那么考虑一下您应该从不使用共享指针的几种方法也许是有益的。这是一个小小的思想实验。让我们定义一个共享指针类型SF = std::shared_ptr&lt;Foo&gt;。为了考虑引用,而不是传递函数参数,让我们看看类型RSF = std::reference_wrapper&lt;T&gt;。也就是说,如果我们有一个共享指针SF p(std::make_shared&lt;Foo&gt;());,那么我们可以通过RSF w = std::ref(p); 制作一个具有值语义的引用包装器。设置就这么多。

现在,每个人都知道指针容器是雷区。所以std::vector&lt;Foo*&gt; 维护起来将是一场噩梦,许多错误都是由于生命周期管理不当引起的。从概念上讲,更糟糕的是,谁拥有容器存储其指针的对象永远不清楚。指针甚至可以是指向动态对象、自动对象和垃圾的指针的混合。谁也说不清。所以标准的解决方案是改用std::vector&lt;SF&gt;。这是使用共享指针的正确方法。另一方面,你绝不能使用的是std::vector&lt;RSF&gt;——这是一个无法控制的怪物,实际上与裸指针的原始向量非常相似!例如,尚不清楚您持有引用的对象是否仍然存在。引用共享指针已经破坏了它的全部目的。

对于第二个示例,假设我们像以前一样有一个共享指针SF p。现在我们有了一个函数int foo(SF),我们想要同时运行它。通常的std::thread(foo, p) 工作得很好,因为线程构造函数制作了它的参数的副本。但是,如果我们说std::thread(foo, std::ref(p)),我们就会遇到各种各样的麻烦:调用范围内的共享指针可能会过期并销毁对象,并且会留下一个悬空引用和一个无效指针!

我希望这两个公认的相当人为的示例能够对您真正希望共享指针通过 copy 传递时有所了解。在一个设计良好的程序中,应该始终清楚谁负责哪些资源,如果使用得当,共享指针是完成这项工作的绝佳工具。

【讨论】:

  • 您能否详细说明“同时删除对象”的含义?您是否暗示除了通过引用传递任何内容时出现的“通常”危险之外还有任何危险?
  • @wolfgang:我刚刚发表了一点题外话,可以解释这一点 --- 但想象几个并发上下文引用同一个 SP;那么一个人可以reset() 它而另一个人最终不仅会修改对 SP 的引用,而且 带有可能无效的资源指针。
  • 对,这就是我所说的“通常”的危险。但是,如果一个并发线程在我复制它时修改了 SP,我也会遇到麻烦(尽管在 std::SP 的某些实现中我可能会侥幸逃脱)。如果我传递一个 std::string 对象(const std::string& 或按值)也是一样的。
  • @wolfgang: 少一点魔法:如果你不相信被调用者 not 存储对你的指针的引用,那么总是传递一个值,因为那说,“在这里,你负责”。通过 const-reference 传递就像“请帮我等一下”,如果你认为他们可能会放弃它,那就不要。
  • @pbyhistorian:一般来说,你应该对你的问题和你的设计有一个清晰的心智模型,然后就没有那么可怕了。首先,您实际需要共享指针(而不是唯一指针)的时间将非常小。一旦您确实需要共享所有权,您要么为所需的服务支付费用,要么在您已经拥有股份并且可以保证终身使用时使用本地直接引用。如果您处于多线程设置中,您仍然需要考虑同步对托管值本身的访问。
【解决方案2】:

这取决于你想要什么。被调用者是否应该共享对象的所有权?然后它需要自己的shared_ptr 副本。所以按值传递。

如果函数只需要访问调用者拥有的对象,请继续并通过 (const) 引用传递,以避免复制 shared_ptr 的开销。

C++ 中的最佳实践是始终为您的对象明确定义所有权语义。没有通用的“总是这样做”来代替实际的想法

如果您总是按值传递共享指针,则成本会很高(因为复制它们比原始指针要昂贵得多)。如果您从不这样做,那么一开始就没有使用共享指针的意义。

当新函数或对象需要共享指针的所有权时,复制共享指针。

【讨论】:

  • “如果你从不这样做,那么一开始就没有使用共享指针的意义。” - 你想到的是“从不复制”,而是指“按价值传递”。有些人可能只需要存储 shared_ptr 并始终通过 const 引用传递它。通过引用传递更快,因为它不会增加/减少引用计数器。
  • @lionbest:不,我想到了价值传递。如果你从不按值传递,shared_ptr 就没有意义,因为只有一个客户端会持有一个实际的 shared_ptr,然后它就不是共享的
  • 您可以复制void store( shared_ptr&lt;T&gt; const&amp; sharedRef ) { this-&gt;mySharedRef=shredRef;}。如果 shredRef 将按值传递,它将增加计数器两次。当然你也可以void store( shared_ptr&lt;T&gt; sharedRef ) { shredRef.swap(this-&gt;mySharedRef);} 做同样的事情。
【解决方案3】:

如果您通过引用传递它,则通过 const 引用传递它。这清楚地表明您出于性能原因通过 ref 。此外,尽可能使用 make_shared,因为它可以节省间接性,从而提高性能。

【讨论】:

  • make_shared 总是一件好事,但请注意,它只在创建第一个共享指针时起作用。
  • @Kerrek SB:并非总是如此。对于一些典型的实现,std::make_shared 会由于 RTTI 的问题而使可执行映像的二进制大小膨胀一点,无论分配是否真的有利于运行时性能。这对于桌面/服务器平台应该不是问题,但对于资源极其有限的嵌入式环境可能是一个问题。
  • @FrankHB:“RTTI 的东西”怎么会不存在于其他实现中?您将始终为使用的每种新类型增加二进制大小,因此您最多可以尝试避免对同一类型使用两种不同的实现。
  • @KerrekSB:二进制大小会膨胀,因为库使用typeid 进行内部实现。但是,我不能直接使用它,因为类型没有暴露。例如,当启用 RTTI 时,libstdc++ 使用typeid(_Sp_make_shared_tag) 获取allocate_shared/make_shared 分配的shared_ptr 的删除器。如果我在其他地方仍然需要 RTTI,我无法在不修改标头中的源的情况下禁用它。实际上结果 type_info 是一个占位符,不需要运行时类型信息。所以像boost::typeindex::type_id 这样的东西应该会让事情变得更好。
猜你喜欢
  • 2023-04-07
  • 1970-01-01
  • 2016-12-02
  • 2014-08-07
  • 1970-01-01
  • 2016-07-10
  • 2015-09-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多