98

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

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 个地址(而不是整个智能指针),但第一种方式似乎更具可读性,我预计不会推动性能限制。我只是想确保它没有危险。

谢谢你。

4

5 回答 5

178

我想将共享指针传递给函数。你能帮我解决这个问题吗?

当然,我可以帮你。我假设您对 C++ 中的所有权语义有一定的了解。真的吗?

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

好的。

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

  1. 该函数想要共享对象的所有权;
  2. 该函数做了一些专门作用于shared_ptrs 的操作。

你对哪一个感兴趣?

我正在寻找一个普遍的答案,所以我实际上对两者都感兴趣。不过,我很好奇您在案例 2 中的意思。

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

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

确切地。

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

是的。如果它不改变指针,你想通过 const 引用传递。无需复制,因为您不需要共享所有权。那是另一种情况。

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

你分享所有权的那个?好的。你如何分享所有权shared_ptr

通过复制它。

那么该函数将需要复制 a shared_ptr,对吗?

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

不,这是一种悲观主义。如果通过引用传递,函数将别无选择,只能手动进行复制。如果它是按值传递的,编译器将在复制和移动之间选择最佳选择并自动执行。所以,按值传递。

好点子。我必须更频繁地记住“想要速度?传递价值。 ”文章。

等等,例如,如果函数将 存储shared_ptr在成员变量中怎么办?这不会造成冗余副本吗?

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

啊,好主意。

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

在那种情况下,shared_ptr与功能完全无关。如果您想操作指针对象,请获取指针对象,然后让调用者选择他们想要的所有权语义。

我应该通过引用还是按价值来获取指针?

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

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

正确的。

唔。我想你忘记了另一个场景。如果我想共享所有权,但仅取决于特定条件怎么办?

啊,一个有趣的边缘案例。我不希望这种情况经常发生。但是当它发生时,您可以通过值传递并在不需要时忽略副本,或者通过引用传递并在需要时制作副本。

我在第一个选项中冒着一个冗余副本的风险,而在第二个选项中失去了潜在的移动。我不能吃蛋糕也吃吗?

如果您处于真正重要的情况,您可以提供两个重载,一个采用 const 左值引用,另一个采用右值引用。一个复制,另一个移动。完美转发功能模板是另一种选择。

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

于 2012-05-31T03:00:22.587 回答
24

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

void DoSomething(myClass * p);

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

作为函数参数的原始指针不会阻止您在调用代码中使用智能指针,这很重要。

于 2012-05-31T03:39:22.390 回答
5

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

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

于 2012-05-31T02:07:16.500 回答
2

在您的第一个示例中传递值是安全的,但有一个更好的习惯用法。尽可能通过 const 引用传递 - 即使在处理智能指针时我也会说是。您的第二个示例并未完全损坏,但非常!???. 愚蠢的,没有完成任何事情并且破坏了智能指针的部分要点,并且当您尝试取消引用和修改事物时,会让您陷入痛苦的错误世界。

于 2012-05-31T02:07:24.447 回答
0

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

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

表达这一点的成语是:a const ref, like so

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

同样,您向函数的用户保证,您不会将另一个所有者添加到原始指针的所有者列表中。但是,您保留了修改原始指针指向的基础对象的可能性。

CAVEAT:这意味着,如果有人shared_ptr::reset在你调用你的函数之前调用,并且在那一刻它是拥有 raw_ptr 的最后一个 shared_ptr,那么你的对象将被销毁,并且你的函数将操纵一个指向被销毁对象的悬空指针。非常危险!!!

于 2020-01-22T19:45:14.520 回答