传递const shared_ptr <t>&amp;而不仅仅是shared_ptr <t>作为参数

时间:2016-06-03 09:17:34

标签: c++ shared-ptr

当智能指针涉及应用程序时,我已经阅读了很多关于性能问题的讨论。其中一个常见的建议是将智能指针传递给const&amp;而不是像这样的副本:

void doSomething(std::shared_ptr<T> o) {}

void doSomething(const std::shared_ptr<T> &o) {}

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

6 个答案:

答案 0 :(得分:21)

shared_ptr传递给const&的好处是参考计数不必增加然后减少。因为这些操作必须是线程安全的,所以它们可能很昂贵。

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

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

(我假设您有一个用例,其中有一些特定的理由通过shared_ptr而非参考。最常见的原因是所调用的函数可能需要延长物体的寿命。)

更新:关于那些感兴趣的人的错误的更多细节:该程序具有共享的对象并实现了内部线程安全性。它们被放在容器中,功能通常可以延长它们的使用寿命。

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

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

此时,存在的对象的唯一shared_ptr可能是非活动容器中的对象。调用堆栈中的每个其他函数都只引​​用了它。当对象从非活动容器中删除时,该对象可能会被销毁,所有这些引用都将被归结为不再存在的shared_ptr

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

答案 1 :(得分:12)

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

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

关于

  

价格使代码安全性降低,对吗?

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

正如David Schwarz在他的回答中所举例说明的那样,当您通过引用const 别名问题时,您调用的函数会更改或调用更改原始对象的函数,是可能的。根据墨菲定律,它将在最不方便的时间以最高成本发生,并且具有最复杂的难以理解的代码。但无论论证是string还是shared_ptr还是其他什么,都是如此。令人高兴的是,这是一个非常罕见的问题。但请记住,传递shared_ptr个实例。

答案 2 :(得分:5)

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

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

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

  

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

这次我完全同意赫伯:)

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

  

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

答案 3 :(得分:3)

我认为如果目标函数是同步的并且仅在执行期间使用参数并且在返回时不再需要它,则传递const &是完全合理的。在这里节省增加引用次数的成本是合理的 - 因为在这些有限的情况下你并不需要额外的安全性 - 只要你了解其含义并确保代码是安全的。

这与函数需要保存参数(例如在类成员中)以供稍后重新引用时相反。

答案 4 :(得分:3)

如果您的方法没有涉及所有权的修改,那么您的方法通过副本或const引用获取shared_ptr没有任何好处,它会污染API并可能产生开销(如果通过副本传递)

干净的方法是根据你的用例传递const ref或ref的底层类型

void doSomething(const T& o) {}

auto s = std::make_shared<T>(...);
// ...
doSomething(*s);

在方法调用

期间无法释放基础指针

答案 5 :(得分:3)

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

所以,你是对的,std::shared_ptr<T> const&可以用作仅仅std::shared_ptr<T>的性能改进。

你也是正确的,因为存在一些混叠,指针/引用会变成悬空的理论风险。

话虽如此,风险在任何C ++程序中都是潜在的:任何单独使用指针或引用都是一种风险。我认为,与std::shared_ptr<T> const&T&T const&,...的总使用次数相比,T*的少数事件应该是水的下降。 / p>

最后,我想指出传递shared_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

唯一可以传递shared_ptr<T> const&的情况是,如果您不确定是否需要副本,并且因为您已对程序进行了分析并显示此原子增量/减量是您的瓶颈已经决定将副本的创建推迟到只需要它的情况。

这是一个边缘案例,任何使用shared_ptr<T> const&的行为都应该被怀疑最高。