将共享指针作为参数传递


88

如果我声明一个包装在共享指针中的对象:

std::shared_ptr<myClass> myClassObject(new myClass());

然后我想将其作为方法的参数传递:

DoSomething(myClassObject);

//the called method
void DoSomething(std::shared_ptr<myClass> arg1)
{
   arg1->someField = 4;
}

上面是否只是简单地增加了shared_pt的引用计数,并且一切都很酷?还是留下一个悬空的指针?

您仍然应该这样做吗?:

DoSomething(myClassObject.Get());

void DoSomething(std::shared_ptr<myClass>* arg1)
{
   (*arg1)->someField = 4;
}

我认为第二种方法可能更有效,因为它只需要复制1个地址(而不是整个智能指针),但是第一种方法似乎更具可读性,并且我预计不会出现性能限制。我只想确保它没有危险。

谢谢。


14
const std::shared_ptr<myClass>& arg1
船长Obvlious 2012年

3
第二种方法是无效的,如果确实需要共享所有权的方法,第一种是惯用的。但是,DoSomething真的需要共享所有权吗?看来应该只是参考一下……
ildjarn

8
@SteveH:没有,但是如果您的函数实际上不需要它们,为什么要在其调用程序上强制使用特殊对象所有权语义呢?您的功能没有比这更好的功能void DoSomething(myClass& arg1)
ildjarn

2
@SteveH:智能指针的全部目的是解决异常的对象所有权问题-如果您没有这些问题,则不应该首先使用智能指针。;-]shared_ptr<>具体而言,您必须按值传递才能实际共享所有权。
ildjarn

4
无关:通常,std::make_shared它不仅比构造方法更有效,而且更安全std::shared_ptr
R. Martinho Fernandes

Answers:


171

我想将共享指针传递给函数。你能帮我吗?

当然可以,我可以为您提供帮助。我假设您对C ++中的所有权语义有所了解。真的吗?

是的,我对这个主题相当满意。

好。

好的,我只能想到两个理由来shared_ptr争论:

  1. 该功能希望共享对象的所有权;
  2. 该函数执行一些专门针对shared_ptrs的操作。

您对哪一个感兴趣?

我正在寻找一个一般性的答案,所以我实际上对这两者都感兴趣。不过,我很好奇第二种情况下的意思。

此类函数的示例包括std::static_pointer_cast,自定义比较器或谓词。例如,如果您需要从向量中查找所有唯一的shared_ptr,则需要这样的谓词。

嗯,当函数实际上需要操纵智能指针本身时。

究竟。

在这种情况下,我认为我们应该通过引用。

是。而且,如果不更改指针,则希望通过const引用传递。无需共享所有权,因此无需复制。那是另一种情况。

好的,我知道了。让我们谈谈另一种情况。

您共享所有权的人?好。您如何与他人共享所有权shared_ptr

通过复制。

然后该功能将需要制作一个的副本,对shared_ptr吗?

明显。所以我通过引用const传递它并将其复制到局部变量?

不,这是一种悲观。如果通过引用传递,则该功能别无选择,只能手动制作副本。如果按值传递,则编译器将在复制和移动之间选择最佳选择并自动执行。因此,传递价值。

好点子。我必须记得更多的文章是“想要速度?通过价值传递”。

等等,例如,如果函数将shared_ptr变量存储在成员变量中怎么办?那不是多余的副本吗?

该函数可以简单地将shared_ptr参数移到其存储中。移动ashared_ptr很便宜,因为它不会更改任何引用计数。

啊,好主意。

但是我在考虑第三种情况:如果您不想操纵shared_ptr或分享所有权怎么办?

在这种情况下,shared_ptr与功能完全无关。如果要操纵该pointe,请选择一个pointee,然后让调用者选择他们想要的所有权语义。

我应该以参考还是以价值来衡量尖兵?

通常的规则适用。智能指针不会改变任何东西。

如果要复制,请按值传递,如果要避免复制,则按引用传递。

对。

嗯 我认为您忘记了另一种情况。如果我想共享所有权,但仅取决于特定条件怎么办?

啊,有趣的情况。我不希望这种情况经常发生。但是当发生这种情况时,您可以根据需要传递值并忽略该副本,也可以根据需要传递引用并进行复制。

在第一种选择中,我会冒一个冗余副本的风险,而在第二种选择中,我会失去潜在的举动。我不能吃蛋糕也可以吗?

如果您确实需要这样做,则可以提供两个重载,一个重载为const左值引用,另一个重载为一个右值引用。一个复制,另一个移动。完善转发功能模板是另一种选择。

我认为这涵盖了所有可能的情况。非常感谢你。


2
@Jon:干嘛?这就是我应该做A还是我应该做B的全部内容。我认为我们都知道如何通过值/引用传递对象,不是吗?
2012年

9
因为诸如“这是否意味着我将通过引用const来传递它来制作副本?不,那是一种悲观主义”之类的散文对初学者感到困惑。传递对const的引用不会产生副本。
2012年

1
@Martinho我不同意“如果您要操纵pointe,请选择pointe”的规则。如答案所述,不拥有对象的临时所有权可能很危险。在我看来,默认值应该是传递副本以拥有临时所有权,以确保函数调用期间对象的有效性。
radman 2013年

@radman您链接到的答案中的任何方案都不会应用该规则,因此完全不相关。请给我写一个SSCCE,在该引用中您通过传递引用shared_ptr<T> ptr;(即通过void f(T&)调用f(*ptr)),并且该对象不会超过调用的寿命。另外,也可以写一个让您忽略价值void f(T)和罢工的地方。
R. Martinho Fernandes

@Martinho查看我的示例代码以获取简单的自包含正确示例。该示例适用于void f(T&)并涉及线程。我认为我的问题是您的回答语气不鼓励通过shared_ptr的所有权,这是使用它们的最安全,最直观和最简单的方法。我极力反对建议初学者提取对shared_ptr <>所拥有的数据的引用,这会导致滥用的可能性很大,而收益却很小。
radman 2013年

22

我认为人们不必要地害怕将原始指针用作函数参数。如果该函数将不存储指针或以其他方式影响其寿命,则原始指针也可以正常工作,并表示最低的公分母。例如,考虑如何将a传递unique_ptr给以ashared_ptr为参数的函数(通过值或通过const引用)?

void DoSomething(myClass * p);

DoSomething(myClass_shared_ptr.get());
DoSomething(myClass_unique_ptr.get());

将原始指针作为函数参数并不会阻止您在真正重要的调用代码中使用智能指针。


8
如果您使用的是指针,为什么不只使用引用呢?DoSomething(*a_smart_ptr)
Xeo 2012年

5
@Xeo,您是对的-这样会更好。有时您需要允许使用NULL指针。
马克·兰瑟姆

1
如果您在使用原始指针作为输出参数的约定,它更容易在调用代码,一个变量可能会改变,如被发现:比较fun(&x)fun(x)。在后一个示例中,x可以按值传递或作为const ref传递。如上所述,nullptr如果您对输出不感兴趣,它也将允许您通过...
Andreas Magnusson,2012年

2
@AndreasMagnusson:处理输出自另一个源的指针时,指针作为输出参数的约定可能会给人一种错误的安全感。因为在这种情况下,调用是fun(x),并且* x被修改,并且源没有&x来警告您。
Zan Lynx

如果函数调用超出了调用者的范围怎么办?有可能已经调用了共享ptr的析构函数,现在该函数将尝试访问已删除的内存,从而导致UB
Sridhar Thiagarajan

4

是的,关于shared_ptr <>的整个想法是,多个实例可以拥有相同的原始指针,并且只有当shared_ptr <>的最后一个实例被销毁时,才会释放基础内存。

我会避免使用指向shared_ptr <>的指针,因为这样做会破坏目的,因为您现在再次处理raw_pointers。


2

在第一个示例中按值传递是安全的,但是有一个更好的习惯用法。尽可能通过const引用传递-即使在处理智能指针时,我也会说是。您的第二个示例并没有完全打破,但确实如此!???。愚蠢的是,没有完成任何事情并破坏了智能指针的部分含义,当您尝试取消引用和修改事物时,会让您陷入困境的麻烦世界。


3
如果您的意思是原始指针,则没人提倡通过指针传递。如果没有所有权争用,则通过引用肯定是惯用的。重点是,shared_ptr<>具体而言,您必须进行复制才能实际共享所有权;如果您有a的引用或指针,shared_ptr<>则您不会共享任何内容,并且会遇到与普通引用或指针相同的生存期问题。
ildjarn

2
@ildjarn:在这种情况下,传递常量引用是可以的。shared_ptr保证原始的调用者比函数调用的寿命长,因此使用可以安全地使用该函数shared_ptr<> const &。如果需要稍后存储指针,则需要复制(此时必须进行复制,而不是保存引用),但是除非您需要这样做,否则无需承担复制成本。它。我会去在这种情况下,通过恒定的参考....
大卫·罗德里格斯- dribeas

3
@David:“在这种情况下,传递常量引用是可以的。 ”在任何理智的API中都不是-为什么API会强制要求一个甚至没有利用而不仅仅是利用普通const引用的智能指针类型?这几乎与缺乏const正确性一样糟糕。如果您的观点是从技术上讲没有任何伤害,那么我当然同意,但是我认为这不是正确的做法。
ildjarn

2
...另一方面,如果该函数不需要延长对象的生存期,则只需shared_ptr从接口中删除,然后将Plain(const)引用传递给指向的对象。
大卫·罗德里格斯(DavidRodríguez)-dribeas,2012年

3
@David:如果您的API使用者不习惯使用shared_ptr<>该怎么办?现在,您的API确实真是让人头疼,因为它迫使调用者仅使用它就无意义地更改其对象生存期语义。除了说“从技术上讲,没有任何伤害”,我看不出有什么值得提倡的。对于“如果您不需要共享所有权,请不要使用shared_ptr<>” ,我没有任何争议。
ildjarn

0

在函数中, DoSomething 您正在更改类实例的数据成员,myClass 因此您要修改的是托管(原始指针)对象而不是(shared_ptr)对象。这意味着在此函数的返回点,所有指向托管原始指针的共享指针都将看到其数据成员:myClass::someField更改为其他值。

在这种情况下,您将一个对象传递给一个函数,并确保您没有修改它(谈论shared_ptr不是拥有的对象)。

表达这一点的成语是通过:const ref,就像这样

void DoSomething(const std::shared_ptr<myClass>& arg)

同样,您要确保函数的用户,也就是不要将其他所有者添加到原始指针的所有者列表中。但是,您保留了修改原始指针所指向的基础对象的可能性。

CAVEAT:这意味着,如果某种程度上有人shared_ptr::reset在调用您的函数之前调用了它,那时候它是拥有raw_ptr的最后一个shared_ptr,那么您的对象将被销毁,并且您的函数将操纵悬垂的指针指向被销毁的对象。非常危险!!!

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.