传递const shared_ptr <T>&而不仅仅是shared_ptr <T>作为参数

Mik*_*hke 24 c++ shared-ptr

当智能指针涉及应用程序时,我一直在阅读有关性能问题的大量讨论.其中一个常见的建议是将智能指针作为const而不是副本传递,如下所示:

void doSomething(std::shared_ptr<T> o) {}
Run Code Online (Sandbox Code Playgroud)

void doSomething(const std::shared_ptr<T> &o) {}
Run Code Online (Sandbox Code Playgroud)

但是,第二种变体实际上是否会破坏共享指针的目的?我们实际上在这里共享共享指针,因此如果由于某些原因在调用代码中释放指针(想到重入或副作用),则const指针变为无效.共享指针实际应该阻止的情况.我理解const&节省了一些时间,因为没有涉及复制,也没有锁定来管理引用计数.但是价格让代码不那么安全了吧?

Dav*_*rtz 21

传递的优点shared_ptrconst&是,引用计数不必增加,然后降低.因为这些操作必须是线程安全的,所以它们可能很昂贵.

你是完全正确的,你可能会有一个通过引用链接的风险,以后会使链的头部无效.在一个真实世界的后果中,这件事发生在我身上.一个函数shared_ptr在一个容器中找到一个函数,并在调用堆栈中向它传递一个引用.调用堆栈深处的函数从容器中删除了对象,导致所有引用突然引用不再存在的对象.

因此,当您通过引用传递某些内容时,调用者必须确保它在函数调用的生命周期中存活.如果这是一个问题,请不要使用引用传递.

(我假设你有一个用例,其中有一些特定的理由要通过shared_ptr而不是通过引用.最常见的原因是被调用的函数可能需要延长对象的生命周期.)

更新:关于那些感兴趣的bug的更多细节:该程序具有共享的对象并实现了内部线程安全性.它们被放在容器中,功能延长它们的寿命是很常见的.

这种特殊类型的对象可以存在于两个容器中.一个是活动的,一个是活动的.一些操作处理活动对象,一些操作处于非活动对象.在非活动对象上收到命令时发生错误情况,该对象使其处于活动状态,而shared_ptr对象仅由非活动对象容器保持.

非活动对象位于其容器中.通过引用shared_ptr将对容器中的引用传递给命令处理程序.通过一系列引用,shared_ptr最终得到的代码意识到这是一个必须被激活的非活动对象.该对象已从非活动容器中删除(它破坏了非活动容器shared_ptr)并添加到活动容器(其中添加了另一个对shared_ptr传递给"add"例程的引用).

此时,shared_ptr对于存在的对象,唯一可能是非活动容器中的对象.调用堆栈中的每个其他函数都只引​​用了它.从非活动容器中删除对象时,可以销毁该对象,并且所有这些引用都是shared_ptr不再存在的.

花了大约一个月的时间来解开这个问题.

  • 这正是我担心共享 shared_ptr 的情况。 (2认同)

Che*_*Alf 12

首先,不要传递一个shared_ptr调用链,除非有一个被调用的函数可能存储它的副本.传递对引用对象的引用,或者指向该对象的原始指针,或者可能是一个框,具体取决于它是否可选.

但是当你传递a时shared_ptr,最好通过引用传递它const,因为复制a会shared_ptr产生额外的开销.复制必须更新共享引用计数,并且此更新必须是线程安全的.因此,可以(安全地)避免一些低效率.

关于

"价格让代码不那么安全,对吗?

不.价格是天真生成的机器代码的额外间接,但编译器管理它.所以这就是为了避免一个次要但完全不必要的开销,编译器无法为你优化,除非它超级聪明.

正如大卫·施瓦茨在他的答案,举例说明,当你路过参考const别名问题,你又改变来电或调用更改原始对象的函数功能,是可能的.根据墨菲定律,它将在最不方便的时间以最高成本发生,并且具有最复杂的难以理解的代码.但无论论证是a string还是a shared_ptr或者其他什么,这都是如此.令人高兴的是,这是一个非常罕见的问题.但是要记住它,也要传递shared_ptr实例.


Sla*_*ava 5

首先,两者之间存在语义差异:按值传递共享指针表示您的函数将占用底层对象所有权的一部分.将shared_ptr作为const引用传递并不表示除了通过const引用(或原始指针)传递底层对象之外的任何意图,而不是强制此函数的用户使用shared_ptr.所以主要是垃圾.

只要它们在语义上不同,比较那些的性能影响是无关紧要的.

来自https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/

除非您想使用或操纵智能指针本身,例如共享或转移所有权,否则不要将智能指针作为函数参数传递.

这次我完全赞同Herb :)

而另一个引用相同,更直接地回答了这个问题

指南:仅使用非const shared_ptr&参数来修改shared_ptr.仅当您不确定是否要复制并共享所有权时,才使用const shared_ptr&作为参数; 否则使用*代替(或者如果不可为空,则为&)


Mat*_* M. 5

正如C++ 中指出的- shared_ptr:可怕的速度,复制 ashared_ptr需要时间。构造涉及原子增量和破坏原子减量,原子更新(无论是增量还是减量)可能会阻止许多编译器优化(内存加载/存储不能跨操作迁移),并且在硬件级别涉及 CPU 缓存一致性协议确保整个缓存线由进行修改的内核拥有(独占模式)。

所以,你是对的,std::shared_ptr<T> const&可能被用作比仅仅std::shared_ptr<T>.


您也正确地认为,由于某些别名,指针/引用存在悬空的理论风险。

话虽如此,风险已经存在于任何 C++ 程序中:任何一次使用指针或引用都是一种风险。我认为,为数不多的出现std::shared_ptr<T> const&应该是在水滴相比,使用的总数T&T const&T*,...


最后,我想指出传递 ashared_ptr<T> const&奇怪。常见的有以下几种情况:

  • shared_ptr<T>: 我需要一份 shared_ptr
  • T*/ T const&/ T&/ T const&:我需要一个(可能为空)句柄T

下一种情况不太常见:

  • shared_ptr<T>&: 我可以重新安装 shared_ptr

但是通过shared_ptr<T> const&?合法用途非常罕见。

传递shared_ptr<T> const&你想要的只是引用T是一种反模式:shared_ptr当用户可以分配T另一种方式时,你强迫用户使用!大多数情况下 (99,99..%),您不应该关心如何T分配。

您将传递 a 的唯一情况shared_ptr<T> const&是,如果您不确定是否需要副本,并且因为您已对程序进行了概要分析并表明此原子递增/递减是一个瓶颈,您已决定推迟创建仅复制到需要的情况。

这是一个极端情况,shared_ptr<T> const&应该以最高程度的怀疑来看待任何使用。