将shared_ptr <Derived>作为shared_ptr <Base>传递


93

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

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

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& b)

  • 占用对象的函数(即给定对象的最终用户)应采用unique_ptr按值,例如int foo(unique_ptr<bar> b)std::move调用者应将值放入函数中。

  • 延长对象寿命的函数应采用shared_ptr按值,例如int foo(shared_ptr<bar> b)。避免循环引用的通常建议适用。

有关详细信息,请参见Herb Sutter的“基础知识”演讲


8
为什么要通过shared_ptr?为什么没有const-reference呢?
IPC 2012年

2
任何dynamic演员表只需要向下转换。同样,传递派生的指针应该可以正常工作。它将创建一个shared_ptr具有相同引用计数(并增加引用计数)和指向基数的指针的新对象,然后将其绑定到const引用。但是,由于您已经在参考,所以我完全不知道为什么要参考shared_ptr。接一个Base const&电话foo(*bar)
Xeo 2012年

@Xeo:薪火派生的指针(即foo(bar))不工作,至少在2010年MSVC
马特·克莱恩

1
您“显然不起作用”是什么意思?该代码可以编译并正确运行。您在问如何避免创建一个临时shared_ptr函数来传递给函数吗?我很确定没有办法避免这种情况。
Mike Seymour

1
@Seth:我不同意。我认为没有理由按值传递共享指针,也没有理由按引用传递共享指针(所有这些都主张不需要的副本)。推理这里stackoverflow.com/questions/10826541/...
R.费尔南德斯Martinho

Answers:


47

虽然BaseDerived是协变的和原始指针他们会采取相应的行动,shared_ptr<Base>并且shared_ptr<Derived>不是协变的。这dynamic_pointer_cast是处理此问题的正确,最简单的方法。

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

但是,如果您的foo()函数不想参与延长生存期(或者更确切地说,参与对象的共享所有权),那么最好在将其传递给时接受aconst Base&并取消引用。shared_ptrfoo()

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

顺便说一句,由于shared_ptr类型不能是协变的,因此在返回的类型时,跨协变返回类型的隐式转换规则不适用shared_ptr<T>


39
它们不是协变的,但是shared_ptr<Derived>可以隐式转换为shared_ptr<Base>,因此代码应在不使用强制转换的情况下工作。
Mike Seymour

9
嗯,shared_ptr<Ty>有一个构造函数,shared_ptr<Other>如果Ty*可以隐式转换为,则可以接受并进行适当的转换Other*。而且,如果需要转换,static_pointer_cast这里是否合适dynamic_pointer_cast
皮特·贝克尔

是的,但不像问题中那样使用他的参考参数。无论如何,他都需要复印。但是,如果他使用ref来shared_ptr避免引用计数,那么实际上没有充分的理由首先使用a shared_ptr。最好改为使用const Base&
Bret Kuhns,2012年

@PeteBecker请参阅我对Mike的有关转换构造函数的评论。老实说static_pointer_cast,我不知道,谢谢。
Bret Kuhns,2012年

1
@TanveerBadar不确定。也许这在2012年未能编译?(特别是使用Visual Studio 2010或2012)。但是您是完全正确的,如果/ public派生的/类的完整定义对编译器可见,则OP的代码应该绝对编译。
Bret Kuhns

30

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

class Derived : Base
{
};

class用于模板参数;struct用于定义类。(这最多是个玩笑的45%。)
戴维斯·赫林

绝对应该将其视为解决方案,因为它只是缺少公众,所以不需要强制转换。
Alexis Paques

12

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

也就是说,有两种方法可以让我想到:

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

9
不,它们并不便宜,应尽可能通过引用传递。
塞斯·卡内基

6
@SethCarnegie-Herb是否分析了您的代码以查看按值传递是否是瓶颈?
皮特·贝克尔

25
@SethCarnegie-不能回答我问的问题。而且,就其价值而言,我编写了shared_ptrMicrosoft随附的实现。
皮特·贝克尔

6
@SethCarnegie-您有启发式的倒退。除非可以证明需要手动优化,否则通常应该进行手动优化。
皮特·贝克尔

21
如果您需要进行优化,那只是“过早的”优化。我认为在低效率的习惯用法上采用低效率的习惯用法没有问题,无论它是否在特定环境中有所作为。
马克·兰瑟姆

11

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

我有这个问题。该std::shared<derived>不会投给std::shared<base>。我已经预先声明了两个类,以便可以保存指向它们的指针,但是由于我没有#include编译器,因此无法看到一个类是从另一个类派生的。


1
哇,我没想到,但这为我解决了。我特别小心,只在需要的地方包含头文件,因此其中一些仅在源文件中,然后按照您所说的在头文件中声明它们。
jigglypuff

愚蠢的编译器是愚蠢的。这是我的问题。谢谢!
Tanveer Badar
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.