【问题标题】:Passing shared_ptr<Derived> as shared_ptr<Base>将 shared_ptr<Derived> 作为 shared_ptr<Base> 传递
【发布时间】:2012-11-04 09:43:27
【问题描述】:

将派生类型的shared_ptr 传递给采用基类型shared_ptr 的函数的最佳方法是什么?

我通常通过引用传递shared_ptrs 以避免不必要的复制:

int foo(const shared_ptr<bar>& ptr);

但如果我尝试做类似的事情,这不起作用

int foo(const shared_ptr<Base>& ptr);

...

shared_ptr<Derived> bar = make_shared<Derived>();
foo(bar);

我可以使用

foo(dynamic_pointer_cast<Base, Derived>(bar));

但这似乎不是最理想的,原因有两个:

  • dynamic_cast 对于简单的派生到基类转换来说似乎有点过分。
  • 据我了解,dynamic_pointer_cast 创建了指针的副本(尽管是临时副本)以传递给函数。

有没有更好的解决方案?

后人更新:

原来是缺少头文件的问题。此外,我在这里尝试做的事情被认为是反模式。一般来说,

  • 不影响对象生命周期的函数(即对象在函数的持续时间内保持有效)应该采用普通引用或指针,例如int foo(bar&amp; b).

  • 使用一个对象的函数(即给定对象的最终用户)应该按值获取unique_ptr,例如int foo(unique_ptr&lt;bar&gt; b)。调用者应该 std::move 将值放入函数中。

  • 延长对象生命周期的函数应按值采用shared_ptr,例如int foo(shared_ptr&lt;bar&gt; b)。避免circular references 的通常建议适用。

详情请参阅 Herb Sutter 的Back to Basics talk

【问题讨论】:

  • 为什么要传递shared_ptr?为什么没有 bar 的 const 引用?
  • 任何dynamic 转换仅用于向下转换。此外,传递派生指针应该可以正常工作。它将创建一个新的shared_ptr,具有相同的引用计数(并增加它)和一个指向基址的指针,然后绑定到 const 引用。但是,由于您已经在参考,因此我完全不明白您为什么要使用 shared_ptr。拨打Base const&amp; 并致电foo(*bar)
  • @Xeo:至少在 MSVC 2010 中,传递派生指针(即foo(bar))不起作用。
  • “显然行不通”是什么意思?代码编译并正确运行;你是在问如何避免创建一个临时的shared_ptr 来传递给函数?我很确定没有办法避免这种情况。
  • @Seth:我不同意。我认为有理由通过值传递共享指针,并且几乎没有理由通过引用传递共享指针(所有这些都没有提倡不需要的副本)。在这里推理stackoverflow.com/questions/10826541/…

标签: c++ casting c++11 shared-ptr smart-pointers


【解决方案1】:

如果您忘记在派生类上指定 public 继承,也会发生这种情况,即,如果您像我一样写这个:

class Derived : Base
{
};

代替:

class Derived : public Base
{
};

【讨论】:

  • class 用于模板参数; struct 用于定义类。 (这最多 45% 是个玩笑。)
  • 这绝对应该被认为是解决方案,不需要演员,因为它只是缺少公共。
  • 救了我!非常感谢。我想知道为什么强制转换只适用于公共继承,你能详细说明一下吗?
【解决方案2】:

虽然BaseDerived 是协变的并且指向它们的原始指针会相应地起作用,但shared_ptr&lt;Base&gt;shared_ptr&lt;Derived&gt;协变的。 dynamic_pointer_cast 是处理此问题的正确且最简单的方法。

编辑:static_pointer_cast 会更合适,因为您正在从派生转换为基础,这是安全的,不需要运行时检查。请参阅下面的 cmets。)

但是,如果您的 foo() 函数不希望参与延长生命周期(或者更确切地说,参与对象的共享所有权),那么最好接受 const Base&amp; 并取消引用shared_ptr 传递给 foo() 时。

void foo(const Base& base);
[...]
shared_ptr<Derived> spDerived = getDerived();
foo(*spDerived);

顺便说一句,因为shared_ptr 类型不能是协变的,所以当返回shared_ptr&lt;T&gt; 的类型时,跨协变返回类型的隐式转换规则不适用。

【讨论】:

  • 它们不是协变的,但 shared_ptr&lt;Derived&gt; 可以隐式转换为 shared_ptr&lt;Base&gt;,因此代码应该在没有强制转换的情况下工作。
  • 嗯,shared_ptr&lt;Ty&gt; 有一个构造函数,它接受 shared_ptr&lt;Other&gt; 并在 Ty* 隐式转换为 Other* 时进行适当的转换。如果需要演员表,static_pointer_cast 是合适的,而不是dynamic_pointer_cast
  • 是的,但不是他的参考参数,如问题所示。无论如何,他都需要复印一份。但是,如果他使用对shared_ptr 的引用来避免引用计数,那么首先就没有充分的理由使用shared_ptr。最好改用const Base&amp;
  • @PeteBecker 查看我对 Mike 关于转换构造函数的评论。老实说,我不知道static_pointer_cast,谢谢。
  • @TanveerBadar 不确定。也许这在 2012 年未能编译? (特别是使用 Visual Studio 2010 或 2012)。但是你是绝对正确的,如果 /publiclyderived/ 类的完整定义对编译器可见,则 OP 的代码绝对应该编译。
【解决方案3】:

还要检查包含派生类完整声明的头文件的#include 是否在源文件中。

我遇到了这个问题。 std::shared&lt;derived&gt; 不会转换为 std::shared&lt;base&gt;。我已经前向声明了这两个类,以便我可以保存指向它们的指针,但是因为我没有 #include,编译器无法看到一个类是从另一个类派生的。

【讨论】:

  • 哇,我没想到,但这为我解决了问题。我非常小心,只在我需要的地方包含头文件,所以其中一些只在源文件中,并按照你所说的在头文件中转发。
  • 愚蠢的编译器是愚蠢的。这是我的问题。谢谢!
【解决方案4】:

听起来你太努力了。 shared_ptr 复制成本低;这是它的目标之一。通过引用传递它们并没有真正完成太多。如果您不想共享,请传递原始指针。

也就是说,有两种方法可以做到这一点,我能想到的就是:

foo(shared_ptr<Base>(bar));
foo(static_pointer_cast<Base>(bar));

【讨论】:

  • 不,复制它们并不便宜,它们应该尽可能通过引用传递。
  • @SethCarnegie - Herb 是否分析了您的代码以查看按值传递是否是瓶颈?
  • @SethCarnegie - 这没有回答我提出的问题。而且,为了它的价值,我编写了 Microsoft 发布的 shared_ptr 实现。
  • @SethCarnegie - 你的启发式倒退了。手部优化通常不应进行除非您可以证明它们是必要的。
  • 如果您需要进行优化,这只是“过早”的优化。我认为采用高效的习语而不是低效的习语没有问题,无论它在特定的上下文中是否有所作为。
猜你喜欢
  • 1970-01-01
  • 2017-08-06
  • 2010-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-21
相关资源
最近更新 更多