将共享指针作为参数传递

Passing shared pointers as arguments

提问人:Steve H 提问时间:5/31/2012 最后编辑:R. Martinho FernandesSteve H 更新时间:6/16/2023 访问量:98826

问:

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

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

谢谢。

C++ 11 共享 PTR C++FAQ

评论

16赞 Captain Obvlious 5/31/2012
const std::shared_ptr<myClass>& arg1
3赞 ildjarn 5/31/2012
第二种方式是被打破的,如果你真的需要你的函数来分享所有权,第一种方式是惯用的。但是,真的需要分享所有权吗?看起来它应该只接受一个参考......DoSomething
8赞 ildjarn 5/31/2012
@SteveH :它没有,但是如果函数实际上不需要它们,为什么还要对其调用者强制使用特殊的对象所有权语义呢?您的函数不会像 .void DoSomething(myClass& arg1)
2赞 ildjarn 5/31/2012
@SteveH :智能指针的全部目的是处理非同寻常的对象所有权问题——如果你没有这些,你一开始就不应该使用智能指针。具体而言,您必须按价值传递才能实际共享所有权。shared_ptr<>
4赞 R. Martinho Fernandes 5/31/2012
无关:一般来说,不仅效率更高,而且比构造函数更安全std::make_sharedstd::shared_ptr

答:

5赞 R Samuel Klatchko 5/31/2012 #1

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

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

2赞 djechlin 5/31/2012 #2

在第一个示例中按值传递是安全的,但有一个更好的习惯用语。如果可能的话,通过常量引用传递 - 即使在处理智能指针时,我也会说是。你的第二个例子并没有完全坏,但它非常.愚蠢,什么都没做,打败了聪明指针的部分意义,当你试图取消引用和修改事物时,会让你陷入一个痛苦的错误世界。!???

评论

3赞 ildjarn 5/31/2012
没有人提倡通过指针传递,如果你指的是原始指针。如果没有所有权争夺,则通过引用传递当然是惯用的。关键是,具体而言,您必须制作一份副本才能实际共享所有权;如果您有指向 A 的引用或指针,那么您不会共享任何内容,并且会遇到与普通引用或指针相同的生存期问题。shared_ptr<>shared_ptr<>
2赞 David Rodríguez - dribeas 5/31/2012
@ildjarn:在这种情况下,传递常量引用是可以的。调用方的原始函数保证比函数调用更持久,因此函数在使用 .如果它需要存储指针以备后用,则需要复制(此时必须进行复制,而不是保留引用),但除非需要这样做,否则无需产生复制成本。在这种情况下,我会传递一个持续的参考......shared_ptrshared_ptr<> const &
3赞 ildjarn 5/31/2012
@David : “在这种情况下,传递常量引用是可以的。在任何理智的 API 中都不是 -- 为什么 API 会强制要求它甚至没有利用的智能指针类型,而不仅仅是采用普通的常量引用?这几乎和缺乏常量正确性一样糟糕。如果你的观点是,从技术上讲,它没有伤害任何东西,那么我当然同意,但我不认为这是正确的做法。
2赞 David Rodríguez - dribeas 5/31/2012
...另一方面,如果该函数不需要延长对象的生存期,那么你可以从接口中删除它,并将一个普通的 () 引用传递给指向的对象。shared_ptrconst
3赞 ildjarn 5/31/2012
@David : 如果你的 API 使用者一开始就没有使用怎么办?现在,您的 API 实际上只是一个痛头,因为它迫使调用者毫无意义地更改其对象生存期语义,只是为了使用它。我看不出有什么值得提倡的,除了说“这在技术上没有伤害任何东西”。我看不出“如果你不需要共享所有权,就不要使用”有任何争议。shared_ptr<>shared_ptr<>
196赞 R. Martinho Fernandes 5/31/2012 #3

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

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

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

好。

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

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

你对哪一个感兴趣?

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

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

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

完全。

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

是的。如果它不更改指针,则希望通过常量引用进行传递。无需复制,因为您不需要共享所有权。这是另一种情况。

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

你分享所有权的那个?还行。您如何与?shared_ptr

通过复制它。

那么函数就需要做一个副本,对吗?shared_ptr

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

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

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

等等,例如,如果函数将 存储在数据成员中呢?那不会成为多余的副本吗?shared_ptr

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

啊,好主意。

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

在这种情况下,与函数完全无关。如果要操作指针,请取一个指针,并让调用方选择他们想要的所有权语义。shared_ptr

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

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

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

右。

嗯。我想你忘记了另一种情况。如果我想分享所有权,但只取决于某个条件怎么办?

啊,一个有趣的边缘案例。我不认为这种情况会经常发生。但是,当它发生时,你可以按值传递,如果你不需要它,可以忽略它,或者通过引用传递,如果你需要它,可以制作副本。

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

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

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

评论

2赞 sbi 6/7/2012
@Jon:做什么的?这都是关于我应该做 A 还是应该做 B。我想我们都知道如何通过值/引用传递对象,不是吗?
9赞 Jon 6/8/2012
因为像这样的散文“这是否意味着我会通过引用 const 来制作副本?不,这是一种悲观“,这让初学者感到困惑。传递对 const 的引用不会生成副本。
1赞 radman 3/9/2013
@Martinho 我不同意“如果你想操纵指针,就拿一个指针”的规则。如答案所述,不临时拥有对象可能很危险。在我看来,默认设置应该是传递一个副本作为临时所有权,以保证对象在函数调用期间的有效性。
0赞 R. Martinho Fernandes 3/9/2013
@radman 您链接到的答案中没有应用该规则的方案,因此它完全无关紧要。请给我写一个 SSCCE,您可以在其中通过引用从 a 传递(即 ,调用 ),并且对象不会超过调用。或者,写一个你通过价值和麻烦罢工的地方。shared_ptr<T> ptr;void f(T&)f(*ptr)void f(T)
0赞 radman 3/9/2013
@Martinho查看我的示例代码,了解简单的自包含正确示例。该示例适用于线程并涉及线程。我认为我的问题是,你回答的语气不鼓励传递shared_ptr的所有权,这是最安全、最直观和最不复杂的使用方式。我非常反对建议初学者提取对shared_ptr<>拥有的数据的引用,滥用的可能性很大,好处很小。void f(T&)
32赞 Mark Ransom 5/31/2012 #4

我认为人们不必要地害怕使用原始指针作为函数参数。如果函数不打算存储指针或以其他方式影响其生存期,则原始指针同样有效,并表示最低公分母。例如,考虑如何将 a 传递给一个将 a 作为参数的函数,无论是通过值还是通过 const 引用?unique_ptrshared_ptr

void DoSomething(myClass * p);

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

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

评论

9赞 Xeo 5/31/2012
如果您使用的是指针,为什么不直接使用引用呢?DoSomething(*a_smart_ptr)
6赞 Mark Ransom 5/31/2012
@Xeo,你是对的——那会更好。但是,有时您需要允许 NULL 指针的可能性。
1赞 Andreas Magnusson 8/29/2012
如果您有一个使用原始指针作为输出参数的约定,那么在调用代码时更容易发现变量可能会更改,例如:compare to .在后一个示例中,可以按值传递,也可以作为常量引用传递。如上所述,如果您对输出不感兴趣,它也将允许您通过......fun(&x)fun(x)xnullptr
2赞 Zan Lynx 10/3/2013
@AndreasMagnusson:在处理从其他源传入的指针时,指针作为输出参数的约定可能会给人一种虚假的安全感。因为在这种情况下,调用是 fun(x) 并且 *x 被修改,并且源没有 &x 来警告您。
0赞 Sridhar Thiagarajan 10/21/2019
如果函数调用的生存期超出了调用方的作用域,该怎么办?共享 ptr 的析构函数可能已被调用,现在该函数将尝试访问已删除的内存,从而导致 UB
1赞 organicoman 1/23/2020 #5

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

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

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

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

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

注意:这意味着,如果有人在你调用函数之前通过某种方式调用,并且此时它是拥有raw_ptr的最后一个shared_ptr,那么你的对象将被销毁,你的函数将操纵一个悬空的指针到被销毁的对象。非常危险!!shared_ptr::reset