【问题标题】:How do I iterate through a sequence of shared_ptr objects?如何遍历一系列 shared_ptr 对象?
【发布时间】:2013-06-21 15:07:06
【问题描述】:

这更像是一个样式问题而不是性能问题。我刚刚将(大部分)我的指针转换为 shared_ptr 对象,并且不情愿地接受了weak_ptrs 作为原始指针的替代品。我的问题是,迭代共享指针对象的序列(比方说向量)的首选方法是什么?这是我一直在做的事情:

std::vector<std::shared_ptr<A>> my_sequence;
// Do something to fill my_sequence;

for (std::shared_ptr<A> const& ptr : my_sequence)
{
  ptr->AMethod();
}

这违反了 *don't use shared_ptr references* 规则,那么有什么好的替代方法,为什么?

我要问的问题是;该技术是否稳健,即。对于 AMethod() 超小和 my_sequence 超大,此方法是否会由于 shared_ptr 副本而开始不必要地阻碍性能?它可读吗?简单吗?

【问题讨论】:

  • auto 会做什么? (WWAD)
  • 创建shared_ptr的副本?
  • 我自己对此很好奇,因为我还不是很懂 C++11,也没有可用的编译器来摆弄
  • 如果您在基于范围的 for 中使用引用,那么您将不会制作不必要的副本。你正在做的很好,auto&amp; ptr 也很好。
  • shared_ptr 不是灵丹妙药。它应该在实际共享所有权时使用。非共享所有权由unique_ptr 表示,非所有权由(原始)引用表示。 weak_ptr 用于必须遵守但不拥有的shared_ptr……但通常不是针对陈旧指针的良好防御做法。这将是最小的公分母,不是良好的编程习惯。

标签: c++ c++11 reference shared-ptr


【解决方案1】:

不使用 shared_ptr 引用的原因是因为您破坏了它试图保护自己免受伤害的机制。 IE。有一个潜在的悬空指针。如果您正在遍历容器,这应该不是问题,因为对象不会意外地消失在您身上。您只是不应该存储您以后可能会使用的 shared_ptr 引用。

即这很糟糕:

struct Y
{
    int y;
    Y() : y(1) {}
};

struct X
{
     shared_ptr<Y>& ref_shared_ptr_y;
     X(shared_ptr<Y>& y) : ref_shared_ptr_y(y) {}
};

void main()
{
    shared_ptr<Y> shared_ptr_y(new Y());
    X x(shared_ptr_y);
    y.reset();
    x.ref_shared_ptr_y->y;  // UB since x.ref_shared_ptr_y was deleted
}

shared_ptr 仅应在您确实需要在 2 个或更多位置之间共享对象所有权时使用。否则会导致不必要的开销,并表明您还没有真正考虑过所有者关系。如果您的所有权仅限于一个位置,请使用unique_ptr

【讨论】:

  • 这扩展到原始指针和引用与一般shared_pointer。如果没有所有权问题,shared_pointer 是错误的工具。 (上帝禁止你将两个 shared_pointers 独立链接到同一个对象。)
  • @Potatoswatter 所以你是说我的方法没问题,只要共享指针实际上是有保证的?
  • @ausairman 好吧,不。你知道吗……我会写一个答案。待命。
【解决方案2】:

首先,免责声明:

shared_ptr 不是灵丹妙药。它应该在实际共享所有权时使用。非共享所有权由unique_ptr 表示,非所有权由(原始)引用表示。 weak_ptr 用于必须遵守但不拥有的shared_ptr……但通常不是针对陈旧指针的良好防御做法。默认为shared_ptr 直接进入最小公分母;这是糟糕的编程习惯。


这里的选择是在shared_ptr&lt; T &gt;shared_ptr&lt; T const &gt;shared_ptr&lt; T &gt; const &amp;T const &amp;T &amp; 之间。假设您没有改变任何东西,但是序列可以改变它的对象,所以const 是可取的。这将范围缩小到 shared_ptr&lt; T const &gt;shared_ptr&lt; T &gt; const &amp;T const &amp;

让我们将问题重新表述为,

shared_ptr&lt; T &gt; const &amp; 是正确的选择吗?

据此评估备选方案。

  • shared_ptr&lt; T const &gt;(按值传递共享指针)

    • +:直接的值语义;可以通过shared_ptr&lt; T &gt;
    • +:可复制或moved 扩大所有权池
    • +:将const 正确性传播到被调用函数
    • +:对对象的访问速度很快:包含直接指针参数
    • –:传递是昂贵的:复制两个机器字并原子接触一个引用计数
    • –:被调用函数与所有权语义有关
  • shared_ptr&lt; T &gt; const &amp;(通过引用传递共享指针)

    • +:传递与直接对象引用一样便宜
    • +:可复制以扩大所有权池
    • –: 失去const 被调用函数的正确性

      • 如果您改用shared_ptr&lt; T const &gt; const &amp;,您将传递一个临时副本,并以这种替代方法和按值传递的缺点告终。
    • –: 对对象的访问需要通过额外的间接途径

    • –:被调用函数与所有权语义有关
  • T const &amp;(直接引用)

    • +:保留const的正确性
    • +:调用者不关心所有权语义
    • +:快速通过,一个机器字
    • +:快速访问函数的直接指针参数(即使这样)
    • –: 不能授予所有权

权衡一下,如果所有权没有被扩展(例如通过返回一个保留参数的对象),你真的应该传递一个简单的引用。在这种情况下,函数应该很少关心它的参数是如何拥有的。让它需要shared_ptr 以最糟糕的方式违反了关注点分离。您不能在拥有本地对象或成员子对象的同时仍然使用该函数。最糟糕的是,一切都变得更加复杂。

如果所有权可能扩展到其他人,这将是首先使用shared_ptr 的理由,那么您应该始终传递shared_ptr&lt; [const] T &gt; 值。是的,它确实会在调用时复制shared_ptr。但是复制shared_ptr 的昂贵部分是更新引用计数,而不是将指针压入堆栈。如果您授予所有权,则无论如何您都希望更新引用计数。获取传递的值和move 它,它 触及引用计数。完成此操作后,您将没有任何优点和许多缺点来传递引用。

【讨论】:

  • 这是一个很好的答案。 Def喜欢赞成/反对分析。但是,我有一些困难,比如你想在这里说什么? But the expensive part of copying a shared_ptr is updating the refcount, not pushing the pointers on the stack. 这里你说给move 呢? Take the passed value and move it, which does not touch the refcount. 你是说shared_ptr 吗?你怎么把它还给调用者?或者如果你不打算将它传回去,你是否建议它?
  • @Adrian 我是说如果你传递了shared_ptr,就按值传递。这将立即更新引用计数(保留它),并且在大多数 ABI 上还传递一个指向对象的直接指针和一个“控制”指针。如果您关心性能,简单地传递东西会很快,但更新引用计数会更慢。传递shared_ptr(按值)的唯一充分理由是,如果您希望将其保留比函数调用更长的时间,因此该函数应将其直接参数和move 以及它的额外保留作为持久性.它是从调用者那里复制的,没有移动,所以一切都很好。
  • 同意。我唯一可以说这不一定是最佳的情况是如果有条件地传递所有权,在这种情况下可能不需要更新 ref 计数器。
猜你喜欢
  • 1970-01-01
  • 2012-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-30
  • 1970-01-01
  • 2017-10-17
  • 2020-08-02
相关资源
最近更新 更多